diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 1bf78ec75..49af1e1b7 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -40,20 +40,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + enable-cache: true - - uses: actions/cache@v3 - with: - path: ${{ env.pythonLocation }} - key: ${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }} - - - name: Upgrade pip tools - run: python -m pip install --upgrade pip setuptools wheel - - run: python -m pip install --upgrade --upgrade-strategy eager -r requirements.txt - - run: python -m pip install --upgrade --upgrade-strategy eager -r requirements-dev.txt + - name: Install dependencies + run: uv sync --locked --extra cpu --no-install-package llama-cpp-python - name: Prepare frontend build run: mkdir -p DashAI/front/build @@ -63,4 +57,4 @@ jobs: name: react-build path: DashAI/front/build - name: Test with pytest - run: pytest -v + run: uv run --no-sync pytest -v diff --git a/.github/workflows/db-migrations.yaml b/.github/workflows/db-migrations.yaml index b36e9a721..dac7cf502 100644 --- a/.github/workflows/db-migrations.yaml +++ b/.github/workflows/db-migrations.yaml @@ -19,15 +19,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + enable-cache: true - name: Install dependencies - run: | - pip install --upgrade --upgrade-strategy eager -r requirements.txt - pip install --upgrade --upgrade-strategy eager -r requirements-dev.txt + run: uv sync --locked --extra cpu --no-install-package llama-cpp-python - name: Set DB env vars run: | @@ -37,7 +36,7 @@ jobs: - name: Show Alembic info run: | - alembic --version + uv run --no-sync alembic --version echo "DB will be at: $DATABASE_URL" - name: Prepare temp dir @@ -48,15 +47,15 @@ jobs: # upgrade to head - name: Upgrade to head run: | - alembic -x url="$DATABASE_URL" upgrade head + uv run --no-sync alembic -x url="$DATABASE_URL" upgrade head # Checks downgrade and upgrade again (reversibility) - name: Downgrade to base and upgrade again (reversibility check) run: | - alembic -x url="$DATABASE_URL" downgrade base - alembic -x url="$DATABASE_URL" upgrade head + uv run --no-sync alembic -x url="$DATABASE_URL" downgrade base + uv run --no-sync alembic -x url="$DATABASE_URL" upgrade head - name: Check for pending autogenerate (python-based) env: PYTHONPATH: "${PYTHONPATH}:." - run: python -m scripts.ci_alembic_check + run: uv run --no-sync python -m scripts.ci_alembic_check diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6597713c3..be0549043 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -26,13 +26,16 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.10" - cache: "pip" - cache-dependency-path: | - requirements.txt - requirements-dev.txt + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + # Installed into the system Python because the Docusaurus build + # invokes `python scripts/generate_components.py` directly. - name: Install DashAI (needed for component introspection) - run: pip install -e . + run: uv pip install --system -e . - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 756af2aad..149aaa9dd 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -23,10 +23,13 @@ jobs: with: python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v6 + + # Pillow goes to the system Python because the institutions-readme-sync + # hook is `language: system` and runs `python scripts/render_institutions.py`. - name: Install pre-commit - run: | - python -m pip install --upgrade pip - pip install pre-commit Pillow + run: uv pip install --system pre-commit Pillow - name: Cache pre-commit uses: actions/cache@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a81b38615..78431f7c7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -59,13 +59,11 @@ jobs: with: python-version: "3.12" - - name: Install build tools - run: | - python -m pip install --upgrade pip - pip install build twine + - name: Install uv + uses: astral-sh/setup-uv@v6 - name: Build Python package - run: python -m build + run: uv build - name: Verify frontend is included in wheel run: | @@ -75,13 +73,13 @@ jobs: fi - name: Check distribution metadata - run: twine check dist/* + run: uvx twine check dist/* - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: twine upload --skip-existing dist/* + run: uvx twine upload --skip-existing dist/* # ============================================================ # 3. Build Windows Executable @@ -97,19 +95,19 @@ jobs: with: name: react-build path: DashAI/front/build - - name: Set up Python 3.12 - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller - name: Build executable shell: cmd run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Install Inno Setup run: choco install innosetup -y @@ -138,19 +136,20 @@ jobs: with: name: react-build path: DashAI/front/build - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller pywebview + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller brew install create-dmg - name: Build ARM64 App Bundle run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Sign App Bundle (ad-hoc) run: | @@ -190,19 +189,20 @@ jobs: with: name: react-build path: DashAI/front/build - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller pywebview + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller brew install create-dmg - name: Build x86_64 App Bundle run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Sign App Bundle (ad-hoc) run: | @@ -246,14 +246,17 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v6 + # python-appimage drives its own embedded pip, so it stays pip-based - name: Install build tooling run: | python -m pip install --upgrade pip - pip install build python-appimage + pip install python-appimage sudo apt-get update sudo apt-get install -y libfuse2 imagemagick librsvg2-bin - name: Build wheel (frontend bundled) - run: python -m build --wheel + run: uv build --wheel - name: Verify frontend is included in wheel run: | if ! unzip -l dist/*.whl | grep -q 'DashAI/front/build'; then @@ -304,10 +307,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Extract version from setup.py + - name: Extract version from pyproject.toml id: version run: | - VERSION=$(grep -m 1 ' version=' setup.py | sed -E 's/.*version="([^"]+)".*/\1/') + VERSION=$(grep -m 1 '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)"/\1/') echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Download Windows installer diff --git a/.gitignore b/.gitignore index 54c488e68..ed688c520 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ build/ develop-eggs/ dist/ downloads/ +!DashAI/back/dependencies/downloads/ +!tests/back/downloads/ eggs/ .eggs/ lib/ @@ -202,3 +204,8 @@ docs/node_modules/ !docs/docs/build/** !docs/i18n/**/build/ !docs/i18n/**/build/** + +# HuggingFace datasets cache generada al correr los tests +tests/**/csv/default-*/ +tests/**/json/default-*/ +tests/**/tests_back_models_*.lock diff --git a/CLAUDE.md b/CLAUDE.md index f63722680..ef035fe8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,26 +11,31 @@ DashAI is a desktop/web graphical toolbox for training, evaluating, and deployin ### Backend ```bash -# Install -pip install -e . -r requirements-dev.txt -pre-commit install +# Install (uv creates .venv, installs the package editable + dev deps) +uv sync # add --extra cpu on machines without NVIDIA GPU +uv run pre-commit install -# Run dev server -python -m DashAI --no-browser --logging-level DEBUG +# Run dev server (if you synced with --extra cpu/cuda, pass the same +# --extra to uv run; a plain uv run re-syncs to the default torch build) +uv run python -m DashAI --no-browser --logging-level DEBUG # Lint / format -ruff check --fix -ruff format +uv run ruff check --fix +uv run ruff format # Run all tests -pytest tests/ +uv run pytest tests/ # Run a single test file or function -pytest tests/back/api/test_components_api.py -v -pytest tests/back/api/test_components_api.py::test_function_name -v +uv run pytest tests/back/api/test_components_api.py -v +uv run pytest tests/back/api/test_components_api.py::test_function_name -v # Database migrations (auto-runs on startup, but also manually) -alembic upgrade head +uv run alembic upgrade head + +# Add / remove a dependency (updates pyproject.toml + uv.lock) +uv add +uv remove ``` ### Frontend diff --git a/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py b/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py new file mode 100644 index 000000000..8d7dc68f0 --- /dev/null +++ b/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py @@ -0,0 +1,31 @@ +"""Add plot_overrides to explainers + +Revision ID: 9a1b2c3d4e5f +Revises: f1a2b3c4d5e6 +Create Date: 2026-07-14 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "9a1b2c3d4e5f" +down_revision: Union[str, None] = "f1a2b3c4d5e6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "global_explainer", sa.Column("plot_overrides", sa.JSON(), nullable=True) + ) + op.add_column( + "local_explainer", sa.Column("plot_overrides", sa.JSON(), nullable=True) + ) + + +def downgrade() -> None: + op.drop_column("local_explainer", "plot_overrides") + op.drop_column("global_explainer", "plot_overrides") diff --git a/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py b/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py new file mode 100644 index 000000000..ca9604eae --- /dev/null +++ b/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py @@ -0,0 +1,28 @@ +"""Add model_name to parameter_history + +Revision ID: a7d2c9e4f1b0 +Revises: f1a2b3c4d5e6 +Create Date: 2026-07-02 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +revision: str = "a7d2c9e4f1b0" +down_revision: Union[str, None] = "f1a2b3c4d5e6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + with op.batch_alter_table("parameter_history", schema=None) as batch_op: + batch_op.add_column(sa.Column("model_name", sa.String(), nullable=True)) + + +def downgrade() -> None: + with op.batch_alter_table("parameter_history", schema=None) as batch_op: + batch_op.drop_column("model_name") diff --git a/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py b/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py new file mode 100644 index 000000000..12fefbac4 --- /dev/null +++ b/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py @@ -0,0 +1,29 @@ +"""Add input_dataset_path to local_explainer + +Revision ID: b2c3d4e5f6a7 +Revises: 9a1b2c3d4e5f +Create Date: 2026-07-15 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +revision: str = "b2c3d4e5f6a7" +down_revision: Union[str, None] = "9a1b2c3d4e5f" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "local_explainer", + sa.Column("input_dataset_path", sa.String(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("local_explainer", "input_dataset_path") diff --git a/DashAI/alembic/versions/c4e8a1d20f3b_make_explainer_name_nullable.py b/DashAI/alembic/versions/c4e8a1d20f3b_make_explainer_name_nullable.py new file mode 100644 index 000000000..0384b810a --- /dev/null +++ b/DashAI/alembic/versions/c4e8a1d20f3b_make_explainer_name_nullable.py @@ -0,0 +1,40 @@ +"""Make explainer name nullable + +The user-facing explainer name was removed from the UI and API. Existing +rows are kept, but new explainers no longer set a name, so the column must +allow nulls. The unique constraint is left in place: it is harmless because +SQLite (and standard SQL) treats NULLs as distinct, so any number of +nameless explainers can coexist. + +Revision ID: c4e8a1d20f3b +Revises: e0f00f71ba44 +Create Date: 2026-07-22 12:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +revision: str = "c4e8a1d20f3b" +down_revision: Union[str, None] = "e0f00f71ba44" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + for table in ("global_explainer", "local_explainer"): + with op.batch_alter_table(table) as batch_op: + batch_op.alter_column( + "name", existing_type=sa.String(), nullable=True + ) + + +def downgrade() -> None: + for table in ("global_explainer", "local_explainer"): + with op.batch_alter_table(table) as batch_op: + batch_op.alter_column( + "name", existing_type=sa.String(), nullable=False + ) diff --git a/DashAI/alembic/versions/e0f00f71ba44_merge_explainer_and_parameter_history_.py b/DashAI/alembic/versions/e0f00f71ba44_merge_explainer_and_parameter_history_.py new file mode 100644 index 000000000..881db38d7 --- /dev/null +++ b/DashAI/alembic/versions/e0f00f71ba44_merge_explainer_and_parameter_history_.py @@ -0,0 +1,26 @@ +"""merge explainer and parameter history heads + +Revision ID: e0f00f71ba44 +Revises: a7d2c9e4f1b0, b2c3d4e5f6a7 +Create Date: 2026-07-22 11:13:21.127590 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'e0f00f71ba44' +down_revision: Union[str, None] = ('a7d2c9e4f1b0', 'b2c3d4e5f6a7') +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/DashAI/back/api/api_v1/api.py b/DashAI/back/api/api_v1/api.py index 1beb875a5..69bb59e3d 100644 --- a/DashAI/back/api/api_v1/api.py +++ b/DashAI/back/api/api_v1/api.py @@ -23,7 +23,6 @@ from DashAI.back.api.api_v1.endpoints.plugins import router as plugins from DashAI.back.api.api_v1.endpoints.predict import router as predict from DashAI.back.api.api_v1.endpoints.runs import router as runs -from DashAI.back.api.api_v1.endpoints.scoring import router as scoring api_router_v1 = APIRouter() api_router_v1.include_router(converters, prefix="/converter") @@ -42,7 +41,6 @@ api_router_v1.include_router(notebook, prefix="/notebook") api_router_v1.include_router(metrics, prefix="/metrics") api_router_v1.include_router(hardware, prefix="/hardware") -api_router_v1.include_router(scoring, prefix="/scoring") api_router_v1.include_router(dataset_source, prefix="/dataset-source") api_router_v1.include_router(datafile_router, prefix="/datafile") api_router_v1.include_router(folders, prefix="/folder") diff --git a/DashAI/back/api/api_v1/endpoints/components.py b/DashAI/back/api/api_v1/endpoints/components.py index 3c2d5cc53..5d7a4a534 100644 --- a/DashAI/back/api/api_v1/endpoints/components.py +++ b/DashAI/back/api/api_v1/endpoints/components.py @@ -3,10 +3,11 @@ import logging from typing import TYPE_CHECKING, Any, Dict, List, Union -from fastapi import APIRouter, Depends, Header, Query, status +from fastapi import APIRouter, Depends, Header, Query, Response, status from fastapi.exceptions import HTTPException from fastapi.responses import StreamingResponse from kink import di, inject +from pydantic import BaseModel from typing_extensions import Annotated from DashAI.back.core.utils import MultilingualString @@ -230,12 +231,199 @@ async def get_components( components_with_related_type, ) + # Reconcile the download state of download required components against the + # filesystem before returning. Downloads happen in the worker process, so + # the in memory registry flag can be stale; a fresh check (a cheap folder + # stat per downloadable component) keeps the list truthful. + for comp_name, component_dict in selected_components.items(): + if getattr(component_dict.get("class"), "REQUIRES_DOWNLOAD", False): + component_registry.refresh_download_status(comp_name) + return [ _filter_by_language(_delete_class(component_dict), accept_language) for component_dict in selected_components.values() ] +@router.get("/{name}/download") +@inject +async def get_component_download_status( + name: str, + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), +): + """Return the reconciled download status of a component. + + Parameters + ---------- + name : str + The component class name. + + Returns + ------- + dict + ``{"downloaded": bool, "requires_download": bool}``. + """ + try: + component_class = component_registry[name]["class"] + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e + requires = bool(getattr(component_class, "REQUIRES_DOWNLOAD", False)) + downloaded = component_registry.refresh_download_status(name) + return {"downloaded": downloaded, "requires_download": requires} + + +@router.post("/{name}/download", status_code=status.HTTP_201_CREATED) +@inject +async def download_component( + name: str, + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), + job_queue=Depends(lambda: di["job_queue"]), +): + """Enqueue a job to download the component's artifacts. + + Parameters + ---------- + name : str + The component class name. + + Returns + ------- + dict + ``{"id": job_id}`` of the enqueued download job. + + Raises + ------ + HTTPException + 404 if unknown; 409 if not downloadable or already downloaded. + """ + from DashAI.back.job.component_download_job import ComponentDownloadJob + + try: + component_class = component_registry[name]["class"] + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e + if not getattr(component_class, "REQUIRES_DOWNLOAD", False): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Component {name} does not require a download", + ) + if component_registry.refresh_download_status(name): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Component {name} is already downloaded", + ) + job = ComponentDownloadJob(component_name=name) + job.set_status_as_delivered() + job_id = job_queue.put(job).id + return {"id": job_id} + + +@router.delete("/{name}/download", status_code=status.HTTP_204_NO_CONTENT) +@inject +async def delete_component_download( + name: str, + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), +): + """Delete a component's downloaded artifacts and reconcile its status. + + Parameters + ---------- + name : str + The component class name. + """ + try: + component_class = component_registry[name]["class"] + except Exception as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e + if not getattr(component_class, "REQUIRES_DOWNLOAD", False): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Component {name} does not support download and cannot be deleted", + ) + component_class.delete() + component_registry.refresh_download_status(name) + return Response(status_code=status.HTTP_204_NO_CONTENT) + + +class RequiredDownloadsParams(BaseModel): + """Request body for resolving nested download-required components. + + Attributes + ---------- + model_name : str or None + The parent component being configured. When set and it still needs a + download, it is included in the result so the caller can gate on a + single list. + parameters : dict + The parameters dict as produced by the configuration UI. + """ + + model_name: Union[str, None] = None + parameters: Dict[str, Any] = {} + + +@router.post("/downloads/required") +@inject +async def get_required_downloads( + params: RequiredDownloadsParams, + accept_language: str | None = Header(default=None), + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), +) -> List[Dict[str, Any]]: + """Return the components a configuration still needs downloaded. + + Walks the ``parameters`` dict for nested components (a component selected as + another component's parameter) and, optionally, checks the parent + ``model_name`` itself. Each component is reconciled against the filesystem so + the answer reflects downloads finished after startup. + + Parameters + ---------- + params : RequiredDownloadsParams + The parent ``model_name`` (optional) and its ``parameters`` dict. + accept_language : str | None + The 'Accept-Language' header used to localize display names. + component_registry : ComponentRegistry + Registry that resolves component classes and download state. + + Returns + ------- + list[dict] + One entry per not-yet-downloaded component, each with ``name``, + ``display_name``, ``parent``, and ``download_size_bytes``. + """ + from DashAI.back.dependencies.downloads.nested import missing_downloads + + missing = missing_downloads(params.parameters, component_registry) + + # Optionally fold in the parent model so callers can gate on one list. + if params.model_name and params.model_name in component_registry: + parent_class = component_registry[params.model_name]["class"] + if getattr( + parent_class, "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): + missing.insert( + 0, + { + "name": params.model_name, + "parent": None, + "download_size_bytes": getattr( + parent_class, "DOWNLOAD_SIZE_BYTES", None + ), + }, + ) + + def _localized_name(name: str) -> str: + display = component_registry[name].get("display_name") + if isinstance(display, MultilingualString): + lang = (accept_language or "en").split("-")[0].lower() + return display.get(lang) + return display or name + + return [ + {**entry, "display_name": _localized_name(entry["name"])} for entry in missing + ] + + @router.get("/{id}/") @inject def get_component_by_id( diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index 38ee9be8f..617afa2bd 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -4,12 +4,14 @@ from fastapi import APIRouter, Depends, status from fastapi.exceptions import HTTPException from kink import di, inject +from pydantic import BaseModel from sqlalchemy import exc, select from DashAI.back.api.api_v1.schemas.explainers_params import ( GlobalExplainerParams, LocalExplainerParams, ValidateDatasetParams, + ValidDatasetsParams, ) from DashAI.back.core.enums.status import ExplainerStatus from DashAI.back.dependencies.database.models import ( @@ -29,6 +31,73 @@ router = APIRouter() +def _apply_overrides(artifacts: list, overrides: dict | None) -> list: + """Replace plotly artifact payloads with stored edited figures. + + Leaves nested inside a ``"grouped"`` selector (see + :class:`DashAI.back.core.artifacts.GroupedArtifacts`), i.e. under each + group's ``artifacts``, are matched by their stamped ``"index"`` just + like top level ones, so a group's plotly artifact can be edited/reset the + same way as a top level one. + + Parameters + ---------- + artifacts : list + Normalized artifact/grouped dicts from ``normalize_artifacts``. + overrides : dict or None + Mapping of ``str(index)`` to an edited plotly figure (JSON string). + + Returns + ------- + list + The artifacts with overridden plotly payloads applied. + """ + if not overrides: + return artifacts + import json + + leaves_by_index = {} + + def collect_leaves(items): + for item in items: + if item.get("type") == "grouped": + for group in item.get("groups", []): + collect_leaves(group.get("artifacts", [])) + else: + leaves_by_index[item.get("index")] = item + + collect_leaves(artifacts) + + for key, figure in overrides.items(): + try: + idx = int(key) + except (TypeError, ValueError): + continue + leaf = leaves_by_index.get(idx) + if leaf is not None and leaf.get("type") == "plotly": + leaf["payload"] = figure if isinstance(figure, str) else json.dumps(figure) + # Flag so the frontend renders the user's edited figure verbatim + # instead of re-applying the app theme (which would clobber the + # edited colors/background). + leaf["overridden"] = True + return artifacts + + +class PlotOverrideBody(BaseModel): + """Request body for saving one plot override. + + Parameters + ---------- + index : int + Artifact index whose payload is being overridden. + figure : object + The edited plotly figure, either a JSON string or a dict. + """ + + index: int + figure: object + + @router.get("/global") @inject async def get_global_explainers( @@ -158,7 +227,8 @@ async def get_global_explanation_plot( Returns ------- List[dict] - A JSON with the explanation plot. + A list of artifact dicts (``{"type", "payload", "title"}``) with the + explanation plots. Raises ------ @@ -168,6 +238,8 @@ async def get_global_explanation_plot( """ import pickle + from DashAI.back.core.artifacts import normalize_artifacts + with session_factory() as db: try: global_explainer = db.scalars( @@ -187,6 +259,7 @@ async def get_global_explanation_plot( ) plot_path = global_explainer[0].plot_path + plot_overrides = global_explainer[0].plot_overrides with open(plot_path, "rb") as file: plot = pickle.load(file) @@ -198,7 +271,7 @@ async def get_global_explanation_plot( detail="Internal database error", ) from e - return plot + return _apply_overrides(normalize_artifacts(plot), plot_overrides) @router.post("/global", status_code=status.HTTP_201_CREATED) @@ -211,8 +284,6 @@ async def upload_global_explainer( Parameters ---------- - name: string - User's name for the explainer run_id: int Id of the run associated with the explainer explainer_name: str @@ -242,7 +313,6 @@ async def upload_global_explainer( ) explainer = GlobalExplainer( - name=params.name, run_id=params.run_id, explainer_name=params.explainer_name, parameters=params.parameters, @@ -441,7 +511,8 @@ async def get_local_explanation_plot( Returns ------- List[dict] - A JSON with the explanation plot. + A list of artifact dicts (``{"type", "payload", "title"}``) with the + explanation plots, typically one per explained instance. Raises ------ @@ -451,6 +522,8 @@ async def get_local_explanation_plot( """ import pickle + from DashAI.back.core.artifacts import normalize_artifacts + with session_factory() as db: try: local_explainer = db.scalars( @@ -470,6 +543,7 @@ async def get_local_explanation_plot( ) plots_path = local_explainer[0].plots_path + plot_overrides = local_explainer[0].plot_overrides with open(plots_path, "rb") as file: plots = pickle.load(file) @@ -481,7 +555,112 @@ async def get_local_explanation_plot( detail="Internal database error", ) from e - return plots + return _apply_overrides( + normalize_artifacts(plots, create_grouped=True), plot_overrides + ) + + +@router.put("/{scope}/plot/{explainer_id}/override") +@inject +async def save_plot_override( + scope: str, + explainer_id: int, + body: PlotOverrideBody, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Persist an edited plotly figure for one artifact of an explanation. + + Parameters + ---------- + scope : str + Either "global" or "local". + explainer_id : int + Id of the explainer whose plot is being edited. + body : PlotOverrideBody + The artifact index and the edited plotly figure. + session_factory : Callable[..., ContextManager[Session]] + Factory yielding a SQLAlchemy session. + + Returns + ------- + dict + ``{"status": "ok"}`` on success. + + Raises + ------ + HTTPException + If the scope is invalid or the explainer does not exist. + """ + import json + + if scope not in ("global", "local"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope" + ) + model = GlobalExplainer if scope == "global" else LocalExplainer + with session_factory() as db: + explainer = db.get(model, explainer_id) + if explainer is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Explainer not found" + ) + overrides = dict(explainer.plot_overrides or {}) + figure = body.figure + overrides[str(body.index)] = ( + figure if isinstance(figure, str) else json.dumps(figure) + ) + explainer.plot_overrides = overrides + db.commit() + return {"status": "ok"} + + +@router.delete("/{scope}/plot/{explainer_id}/override/{index}") +@inject +async def delete_plot_override( + scope: str, + explainer_id: int, + index: int, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Remove a stored plot override, reverting to the computed figure. + + Parameters + ---------- + scope : str + Either "global" or "local". + explainer_id : int + Id of the explainer. + index : int + Artifact index whose override is removed. + session_factory : Callable[..., ContextManager[Session]] + Factory yielding a SQLAlchemy session. + + Returns + ------- + dict + ``{"status": "ok"}``. + + Raises + ------ + HTTPException + If the scope is invalid or the explainer does not exist. + """ + if scope not in ("global", "local"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope" + ) + model = GlobalExplainer if scope == "global" else LocalExplainer + with session_factory() as db: + explainer = db.get(model, explainer_id) + if explainer is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Explainer not found" + ) + overrides = dict(explainer.plot_overrides or {}) + overrides.pop(str(index), None) + explainer.plot_overrides = overrides or None + db.commit() + return {"status": "ok"} @router.post("/local", status_code=status.HTTP_201_CREATED) @@ -494,8 +673,6 @@ async def upload_local_explainer( Parameters ---------- - name: string - User's name for the explainer run_id: int Id of the run associated with the explainer explainer_name: str @@ -529,7 +706,6 @@ async def upload_local_explainer( ) explainer = LocalExplainer( - name=params.name, run_id=params.run_id, explainer_name=params.explainer_name, dataset_id=params.dataset_id, @@ -706,3 +882,90 @@ async def validate_dataset( validation_response["dataset_status"] = "valid" return validation_response + + +@router.post("/local/valid-datasets") +@inject +async def valid_datasets( + params: ValidDatasetsParams, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Return the ids of every dataset that can be explained by a run's model. + + A dataset is valid when it has all the model's input and output columns and + their types match the training dataset. The run, model session and training + dataset are loaded once and every dataset is checked in a single request so + the frontend does not have to validate them one at a time. + + Parameters + ---------- + params : ValidDatasetsParams + The run whose model the datasets must be compatible with. + + Returns + ------- + dict + ``{"valid_dataset_ids": [...]}`` with the ids of the valid datasets. + """ + # get_columns_spec reads only the Arrow schema metadata (column names + + # types), never the rows, so validating every dataset stays cheap even with + # many/large (e.g. image) datasets on the platform. + from DashAI.back.dataloaders.classes.dashai_dataset import get_columns_spec + + with session_factory() as db: + try: + run: Run = db.get(Run, params.run_id) + if not run: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Run not found", + ) + model_session: ModelSession = db.get(ModelSession, run.model_session_id) + if not model_session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Model session not found", + ) + training_dataset: Dataset = db.get(Dataset, model_session.dataset_id) + datasets = db.scalars(select(Dataset)).all() + except exc.SQLAlchemyError as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Internal database error", + ) from e + + required_columns = model_session.input_columns + model_session.output_columns + + training_types = {} + if training_dataset: + try: + training_spec = get_columns_spec(f"{training_dataset.file_path}/dataset") + training_types = { + col: spec.get("type") for col, spec in training_spec.items() + } + except Exception as e: + log.warning(f"Could not read training dataset schema: {e}") + + valid_dataset_ids = [] + for dataset in datasets: + try: + columns_spec = get_columns_spec(f"{dataset.file_path}/dataset") + except Exception as e: + log.warning(f"Could not read dataset {dataset.id} schema: {e}") + continue + + if any(col not in columns_spec for col in required_columns): + continue + + type_mismatch = any( + col in training_types + and training_types[col] != columns_spec[col].get("type") + for col in required_columns + ) + if type_mismatch: + continue + + valid_dataset_ids.append(dataset.id) + + return {"valid_dataset_ids": valid_dataset_ids} diff --git a/DashAI/back/api/api_v1/endpoints/explorers.py b/DashAI/back/api/api_v1/endpoints/explorers.py index 45eedbf25..a536cc97d 100644 --- a/DashAI/back/api/api_v1/endpoints/explorers.py +++ b/DashAI/back/api/api_v1/endpoints/explorers.py @@ -12,6 +12,7 @@ ExplorerCreate, ExplorerResultsOptions, ) +from DashAI.back.core.artifacts import normalize_artifacts from DashAI.back.core.enums.status import ExplorerStatus from DashAI.back.dependencies.database.models import Dataset, Explorer, Notebook @@ -328,7 +329,8 @@ async def get_explorer_results( detail="Error while getting explorer results", ) from e - return results + # Upgrade legacy {"data", "type", "config"} results from plugin explorers + return normalize_artifacts(results) @router.put("/{explorer_id}/results/") diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 8211dbcf9..b67c5359c 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -15,6 +15,7 @@ GenerativeSessionParameterHistory, ProcessData, ) +from DashAI.back.dependencies.downloads.nested import missing_downloads if TYPE_CHECKING: from sqlalchemy.orm import sessionmaker @@ -47,6 +48,31 @@ async def upload_generative_session( detail=f"Model {params.model_name} is not registered.", ) from e + # Guard: model requires download but has not been downloaded -> 409. + # Reconcile against the filesystem so a model downloaded after startup + # (in the worker process) is recognised without an API restart. + if getattr( + model_class, "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"Model {params.model_name} must be downloaded before use." + ), + ) + + # A parameter may select another component that itself needs + # downloading; block until every nested one is present. + nested_missing = missing_downloads(params.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"These components must be downloaded before use: {names}." + ), + ) + # Check if the model is a subclass of GenerativeModel if not issubclass(model_class, BaseGenerativeModel): raise HTTPException( @@ -103,6 +129,7 @@ async def upload_generative_session( session_params_entry = GenerativeSessionParameterHistory( session_id=session.id, parameters=session.parameters, + model_name=session.model_name, modified_at=datetime.now(), ) db.add(session_params_entry) @@ -309,7 +336,9 @@ async def update_generative_session( session_id: int, name: Union[str, None] = None, description: Union[str, None] = None, + model_name: Union[str, None] = None, session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), ): """Update the generative session associated with the provided ID. @@ -321,9 +350,15 @@ async def update_generative_session( New name for the session. description : Union[str, None], optional New description for the session. + model_name : Union[str, None], optional + New model (component name) for the session. Must be a registered + generative model; if it requires a download it must already be + downloaded. session_factory : Callable[..., ContextManager[Session]] A factory that creates a context manager that handles a SQLAlchemy session. The generated session can be used to access and query the database. + component_registry : ComponentRegistry + The DashAI component registry, used to validate the new model. Returns ------- @@ -333,8 +368,11 @@ async def update_generative_session( Raises ------ HTTPException - If the session does not exist, name is invalid, or name already exists. + If the session does not exist, the name is invalid or taken, or the new + model is unknown, not a generative model, or not yet downloaded. """ + from DashAI.back.models.base_generative_model import BaseGenerativeModel + with session_factory() as db: try: session = db.get(GenerativeSession, session_id) @@ -373,7 +411,55 @@ async def update_generative_session( if description is not None: setattr(session, "description", description) - if name is not None or description is not None: + # Validate and apply a model change if provided. A model may be + # selected even when it is not downloaded yet; the chat blocks input + # and offers a download until the weights become available. + if model_name is not None and model_name != session.model_name: + try: + model_class = component_registry[model_name]["class"] + except KeyError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Model {model_name} is not registered.", + ) from e + if not issubclass(model_class, BaseGenerativeModel): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Model {model_name} is not a valid generative model.", + ) + + # Resolve the parameters for the new model: reuse the most + # recent parameters used for it in this session, else fall back + # to the model's schema defaults (its field placeholders). + last_used = ( + db.query(GenerativeSessionParameterHistory) + .filter( + GenerativeSessionParameterHistory.session_id == session_id, + GenerativeSessionParameterHistory.model_name == model_name, + ) + .order_by(GenerativeSessionParameterHistory.modified_at.desc()) + .first() + ) + if last_used is not None: + new_parameters = last_used.parameters + else: + properties = model_class.get_schema().get("properties", {}) + new_parameters = { + key: prop.get("placeholder") for key, prop in properties.items() + } + + session.model_name = model_name + session.parameters = new_parameters + db.add( + GenerativeSessionParameterHistory( + session_id=session.id, + parameters=new_parameters, + model_name=model_name, + modified_at=datetime.now(), + ) + ) + + if name is not None or description is not None or model_name is not None: session.last_modified = datetime.now() db.commit() db.refresh(session) @@ -443,6 +529,7 @@ async def update_generative_session_params( session_params_entry = GenerativeSessionParameterHistory( session_id=session.id, parameters=updated_parameters, + model_name=session.model_name, modified_at=datetime.now(), ) db.add(session_params_entry) @@ -571,26 +658,42 @@ async def get_parameter_history_entry( ) parameters_history = [p.__dict__ for p in parameters_history] + if not parameters_history: + return [] events = [] prev_params = parameters_history[0]["parameters"] + prev_model = parameters_history[0].get("model_name") for i in range(1, len(parameters_history)): curr = parameters_history[i] curr_params = curr["parameters"] + curr_model = curr.get("model_name") changes = [] - for key in curr_params: - old_val = prev_params.get(key) - new_val = curr_params[key] - if old_val != new_val: - changes.append( - { - "parameter": key, - "oldValue": old_val, - "newValue": new_val, - } - ) + # A model switch resets parameters to the new model's own + # values, so the raw parameter diff would be noise; report only + # the model change for that entry. + if curr_model and prev_model and curr_model != prev_model: + changes.append( + { + "parameter": "model", + "oldValue": prev_model, + "newValue": curr_model, + } + ) + else: + for key in curr_params: + old_val = prev_params.get(key) + new_val = curr_params[key] + if old_val != new_val: + changes.append( + { + "parameter": key, + "oldValue": old_val, + "newValue": new_val, + } + ) events.append( { @@ -600,6 +703,7 @@ async def get_parameter_history_entry( } ) prev_params = curr_params + prev_model = curr_model return events diff --git a/DashAI/back/api/api_v1/endpoints/pipelines.py b/DashAI/back/api/api_v1/endpoints/pipelines.py index 5c2b8a210..b0671fdbf 100644 --- a/DashAI/back/api/api_v1/endpoints/pipelines.py +++ b/DashAI/back/api/api_v1/endpoints/pipelines.py @@ -12,6 +12,7 @@ ValidateNodeParams, ValidatePipelineParams, ) +from DashAI.back.core.artifacts import normalize_artifacts from DashAI.back.dependencies.database.models import Dataset, Pipeline from DashAI.back.pipeline.validator.nodes_definitions import NODES from DashAI.back.pipeline.validator.pipeline_validator import PipelineValidator @@ -280,7 +281,7 @@ async def get_pipeline_dataexploration_results( ) results[exploration_id] = { "exploration_type": exploration_type, - "results": result, + "results": normalize_artifacts(result), "name": name, } except Exception as e: diff --git a/DashAI/back/api/api_v1/endpoints/predict.py b/DashAI/back/api/api_v1/endpoints/predict.py index 188a14b02..23da1a336 100644 --- a/DashAI/back/api/api_v1/endpoints/predict.py +++ b/DashAI/back/api/api_v1/endpoints/predict.py @@ -122,7 +122,7 @@ async def get_all_predictions( # Concatenate datasets to predictions dataset_dict = {dataset.id: dataset for dataset in datasets} for prediction in predictions: - prediction.dataset = dataset_dict.get(prediction.dataset_id, None) + prediction.dataset = dataset_dict.get(prediction.dataset_id) return predictions @@ -133,7 +133,12 @@ async def filter_datasets_endpoint( session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), ): """ - Filter datasets that match the column specifications of the train dataset. + Return the ids of every dataset that has all the run model's input columns. + + The run, model session, and every dataset's schema are checked in a single + request (reading only the Arrow schema metadata for each dataset, never its + rows) and only the matching ids are returned, so the frontend does not have + to validate datasets one at a time or fetch full dataset info up front. Parameters ---------- @@ -145,11 +150,9 @@ async def filter_datasets_endpoint( Returns ------- - List[Dataset] - List of datasets that match the column specifications of the train dataset. + dict + ``{"valid_dataset_ids": [...]}`` with the ids of the matching datasets. """ - from pathlib import Path - from DashAI.back.dataloaders.classes.dashai_dataset import get_columns_spec try: @@ -169,16 +172,18 @@ async def filter_datasets_endpoint( input_columns = list(model_session.input_columns) datasets = db.query(Dataset).all() - datasets_filtered = [] + valid_dataset_ids = [] for dataset in datasets: - dataset_path = Path(f"{dataset.file_path}/dataset/") - if dataset_path.exists(): - columns_spec = get_columns_spec(str(dataset_path)) - if all(col in columns_spec for col in input_columns): - datasets_filtered.append(dataset) - else: - logger.warning("Dataset path does not exist: %s", dataset_path) - return datasets_filtered + try: + columns_spec = get_columns_spec(f"{dataset.file_path}/dataset") + except Exception as e: + logger.warning( + "Could not read dataset %s schema: %s", dataset.id, e + ) + continue + if all(col in columns_spec for col in input_columns): + valid_dataset_ids.append(dataset.id) + return {"valid_dataset_ids": valid_dataset_ids} except HTTPException: # Re-raise HTTPExceptions as-is raise diff --git a/DashAI/back/api/api_v1/endpoints/runs.py b/DashAI/back/api/api_v1/endpoints/runs.py index 04959dc63..0d559ea46 100644 --- a/DashAI/back/api/api_v1/endpoints/runs.py +++ b/DashAI/back/api/api_v1/endpoints/runs.py @@ -1,7 +1,7 @@ import logging -from typing import TYPE_CHECKING, Literal, Optional, Union +from typing import TYPE_CHECKING, Union -from fastapi import APIRouter, Depends, Query, Response, status +from fastapi import APIRouter, Depends, Response, status from fastapi.exceptions import HTTPException from kink import di, inject from sqlalchemy import exc, select @@ -17,12 +17,11 @@ Run, RunStatus, ) -from DashAI.back.services.scoring_service import ScoringService +from DashAI.back.dependencies.downloads.nested import missing_downloads if TYPE_CHECKING: from sqlalchemy.orm import sessionmaker - from DashAI.back.dependencies.registry import ComponentRegistry logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) @@ -77,40 +76,25 @@ def get_metrics_for_run(db, run_id: int): @inject async def get_runs( model_session_id: Union[int, None] = None, - include_scores: bool = Query(False), - profile_id: Optional[str] = Query(None), - metric_split: Literal["train", "validation", "test"] = Query("test"), session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), - component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), ): """Retrieve a list of the stored model session runs in the database. The runs can be filtered by model_session_id if the parameter is passed. - Optionally includes computed scores for each run. Parameters ---------- model_session_id: Union[int, None], optional If specified, the function will return all the runs associated with the model session, by default None. - include_scores: bool, optional - If True, compute and include scores for each run, by default False. - profile_id: Optional[str], optional - Scoring profile ID (e.g., "balanced"). Only used if include_scores=True. - If not provided, defaults to first available profile for the session task. - metric_split: str, optional - Which metrics to use: "train", "validation", or "test", by default "test". session_factory : Callable[..., ContextManager[Session]] A factory that creates a context manager that handles a SQLAlchemy session. The generated session can be used to access and query the database. - component_registry : ComponentRegistry - Registry for metric metadata (injected). Returns ------- List[dict] - A list with all selected runs. If include_scores=True, each run includes - a "score" dict with "value" (0-100) and "breakdown" (list of metrics). + A list with all selected runs. Raises ------ @@ -119,7 +103,6 @@ async def get_runs( """ with session_factory() as db: try: - model_session = None if model_session_id is not None: model_session = db.get(ModelSession, model_session_id) if not model_session: @@ -145,40 +128,6 @@ async def get_runs( run.validation_metrics = metrics["validation_metrics"] run.test_metrics = metrics["test_metrics"] - # Compute scores if requested - if include_scores and runs: - scoring_service = ScoringService() - - # Determine profile to use - task_name = model_session.task_name if model_session else None - available_profiles = scoring_service.get_available_profiles(task_name) - - if not profile_id and available_profiles: - profile_id = available_profiles[0]["id"] - - # Only compute if a profile is available - if profile_id: - # Determine which metrics dict to use based on split - metrics_key = f"{metric_split}_metrics" - - # Prepare runs data for scoring - runs_metrics = [ - { - "run_id": run.id, - "metrics": getattr(run, metrics_key, {}) or {}, - } - for run in runs - ] - - # Compute all scores - scores = scoring_service.compute_scores_for_comparison( - runs_metrics, profile_id - ) - - # Attach scores to runs - for run in runs: - run.score = scores.get(run.id) - except exc.SQLAlchemyError as e: log.exception(e) raise HTTPException( @@ -281,7 +230,12 @@ async def get_hyperparameter_optimization_plot( detail="Internal database error", ) from e - return plot + from DashAI.back.core.artifacts import normalize_artifacts + + # Re-normalized on every read (not just on save) so plots pickled before + # this endpoint returned typed artifacts (plain plotly JSON strings) + # still come back in the same {type, payload, title} shape. + return normalize_artifacts(plot)[0] @router.post("/", status_code=status.HTTP_201_CREATED) @@ -289,17 +243,21 @@ async def get_hyperparameter_optimization_plot( async def upload_run( params: RunParams, session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), + component_registry=Depends(lambda: di["component_registry"]), ): """Create a new run. Parameters ---------- - params : int + params : RunParams The parameters of the new run, which includes the model session, model name, run name and description, among others. session_factory : Callable[..., ContextManager[Session]] A factory that creates a context manager that handles a SQLAlchemy session. The generated session can be used to access and query the database. + component_registry : ComponentRegistry + The application component registry, used to check whether the requested + model has been downloaded. Returns ------- @@ -310,6 +268,8 @@ async def upload_run( ------ HTTPException If the model session with id model_session_id is not registered in the DB. + HTTPException + If the model requires a download but has not been downloaded yet (HTTP 409). """ with session_factory() as db: try: @@ -319,6 +279,35 @@ async def upload_run( status_code=status.HTTP_404_NOT_FOUND, detail="Model session not found", ) + # REQUIRES_DOWNLOAD is the static contract; the download state is + # reconciled against the filesystem so a model downloaded after + # startup (in the worker process) is recognised without a restart. + if params.model_name not in component_registry: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"Unknown model '{params.model_name}'", + ) + entry = component_registry[params.model_name] + if getattr( + entry["class"], "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"Model {params.model_name} must be downloaded before use." + ), + ) + # A parameter may select another component (e.g. a classifier) that + # itself needs downloading; block until every nested one is present. + nested_missing = missing_downloads(params.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"These components must be downloaded before use: {names}." + ), + ) run = Run( model_session_id=params.model_session_id, model_name=params.model_name, diff --git a/DashAI/back/api/api_v1/endpoints/scoring.py b/DashAI/back/api/api_v1/endpoints/scoring.py deleted file mode 100644 index 4a2627dd4..000000000 --- a/DashAI/back/api/api_v1/endpoints/scoring.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Scoring and model comparison endpoints.""" - -from typing import TYPE_CHECKING, Optional - -from fastapi import APIRouter, Query - -if TYPE_CHECKING: - pass - -router = APIRouter() - - -@router.get("/profiles") -def get_scoring_profiles( - task_name: Optional[str] = Query(None), -): - """Retrieve available scoring profiles, optionally filtered by task. - - Profiles are fetched from task SCORING_PROFILES defined in the registry. - - Parameters - ---------- - task_name : Optional[str], optional - Task class name (e.g., "TabularClassificationTask"). If specified, - only profiles for that task are returned, by default None. - - Returns - ------- - List[Dict[str, Any]] - List of profile dicts with keys: - - id: str (e.g., "balanced") - - description: str - - weights: Dict[str, float] (metric name → weight) - """ - from DashAI.back.services.scoring_service import ScoringService - - service = ScoringService() - profiles = service.get_available_profiles(task_name) - return profiles diff --git a/DashAI/back/api/api_v1/schemas/explainers_params.py b/DashAI/back/api/api_v1/schemas/explainers_params.py index 3d955ca2f..c8546ba55 100644 --- a/DashAI/back/api/api_v1/schemas/explainers_params.py +++ b/DashAI/back/api/api_v1/schemas/explainers_params.py @@ -2,14 +2,12 @@ class GlobalExplainerParams(BaseModel): - name: str run_id: int explainer_name: str parameters: dict class LocalExplainerParams(BaseModel): - name: str run_id: int explainer_name: str dataset_id: int @@ -21,3 +19,7 @@ class LocalExplainerParams(BaseModel): class ValidateDatasetParams(BaseModel): run_id: int dataset_id: int + + +class ValidDatasetsParams(BaseModel): + run_id: int diff --git a/DashAI/back/config.py b/DashAI/back/config.py index 728c8a760..6f3a0d507 100644 --- a/DashAI/back/config.py +++ b/DashAI/back/config.py @@ -22,3 +22,4 @@ class DefaultSettings(BaseSettings): EXPLANATIONS_PATH: str = "explanations" NOTEBOOK_PATH: str = "notebook" DATAFILE_PATH: str = "datafiles" + COMPONENT_PATH: str = "components" diff --git a/DashAI/back/converters/category/feature_engineering.py b/DashAI/back/converters/category/feature_engineering.py new file mode 100644 index 000000000..5dca3da00 --- /dev/null +++ b/DashAI/back/converters/category/feature_engineering.py @@ -0,0 +1,30 @@ +from typing import Final + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.core.utils import MultilingualString +from DashAI.back.static.icons import Icon + + +class FeatureEngineeringConverter(BaseConverter): + """Base class for converters that derive new features from existing columns. + + Feature engineering converters compute new columns out of one or more + existing columns instead of modifying them in place. Examples include + ColumnArithmetic (arithmetic combinations of two numeric columns), + NumericExpansion (log1p, square, and square-root expansions of a numeric + column), and ColumnConcat (string concatenation of two text/categorical + columns). + + Use these converters to craft new signals for models when the raw + columns alone are not expressive enough. + """ + + CATEGORY = MultilingualString( + en="Feature Engineering", + es="Ingeniería de Características", + pt="Engenharia de Características", + de="Feature-Engineering", + zh="特征工程", + ) + ICON: Final[str] = Icon.Functions.value + COLOR: Final[str] = "rgb(0, 188, 212)" diff --git a/DashAI/back/converters/scikit_learn/simple_imputer.py b/DashAI/back/converters/scikit_learn/simple_imputer.py index 37071f65a..6a56d2ad9 100644 --- a/DashAI/back/converters/scikit_learn/simple_imputer.py +++ b/DashAI/back/converters/scikit_learn/simple_imputer.py @@ -1,3 +1,5 @@ +from typing import TYPE_CHECKING, Union + from sklearn.impute import SimpleImputer as SimpleImputerOperation from DashAI.back.converters.category.basic_preprocessing import ( @@ -20,6 +22,9 @@ from DashAI.back.types.dashai_data_type import DashAIDataType from DashAI.back.types.value_types import Float, Integer +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + class SimpleImputerSchema(BaseSchema): """Schema for configuring the SimpleImputer converter. @@ -103,9 +108,15 @@ class SimpleImputer( Columns with all-missing values are handled according to the ``keep_empty_features`` flag. When ``add_indicator=True``, a - ``MissingIndicator`` binary matrix is stacked onto the output. All - output columns are typed as ``Float64`` in DashAI regardless of the - original column type. + ``MissingIndicator`` binary matrix is stacked onto the output. + + Output typing preserves the original column type whenever the strategy + does not force a fractional result: ``"most_frequent"`` and + ``"constant"`` never perform arithmetic, so the source type (Integer, + Float, or Categorical) is kept. For ``"mean"``/``"median"`` the computed + per-column statistic is inspected, and an originally-Integer column + stays ``Integer`` if that statistic happens to be a whole number (e.g. a + median over an odd count of integers); otherwise it becomes ``Float64``. Wraps ``sklearn.impute.SimpleImputer``. @@ -170,20 +181,73 @@ def __init__(self, **kwargs): """ super().__init__(**kwargs) + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "SimpleImputer": + """Fit the imputer, remembering input types and column order. + + These are needed by ``get_output_type`` to preserve the original + column type instead of always coercing to ``Float64``. + + Parameters + ---------- + x : DashAIDataset + The input dataset to fit the imputer on. + y : DashAIDataset, optional + Ignored; present for API consistency. + + Returns + ------- + SimpleImputer + The fitted imputer instance (self). + """ + if hasattr(x, "types") and x.types is not None: + self._input_types = dict(x.types) + self._input_columns = {name: idx for idx, name in enumerate(x.column_names)} + return super().fit(x, y) + def get_output_type(self, column_name: str = None) -> DashAIDataType: """Return the DashAI data type produced by this converter for a column. Parameters ---------- column_name : str, optional - Not used; all output columns share the - same type. Defaults to None. + The name of the output column. Defaults to None. Returns ------- DashAIDataType - A Float type backed by ``pyarrow.float64()``. + ``Integer`` for the binary ``MissingIndicator`` columns appended + when ``add_indicator=True``. Otherwise, the original column type + for ``"most_frequent"``/``"constant"`` (no arithmetic is performed + on the values), or for ``"mean"``/``"median"``, ``Integer`` if the + source column was an Integer and the computed statistic is a + whole number — otherwise a Float type backed by + ``pyarrow.float64()``. """ import pyarrow as pa + if column_name and str(column_name).startswith("missingindicator_"): + return Integer(arrow_type=pa.int64()) + + input_types = getattr(self, "_input_types", None) + input_type = input_types.get(column_name) if input_types else None + + if self.strategy in ("most_frequent", "constant"): + if input_type is not None: + return input_type + return Float(arrow_type=pa.float64()) + + if isinstance(input_type, Integer): + columns = getattr(self, "_input_columns", None) + statistics = getattr(self, "statistics_", None) + if ( + columns is not None + and statistics is not None + and column_name in columns + ): + value = statistics[columns[column_name]] + if float(value).is_integer(): + return Integer(arrow_type=pa.int64()) + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/simple_converters/column_arithmetic.py b/DashAI/back/converters/simple_converters/column_arithmetic.py new file mode 100644 index 000000000..1396f9dc4 --- /dev/null +++ b/DashAI/back/converters/simple_converters/column_arithmetic.py @@ -0,0 +1,467 @@ +import math +from typing import TYPE_CHECKING, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.feature_engineering import ( + FeatureEngineeringConverter, +) +from DashAI.back.core.schema_fields import ( + bool_field, + enum_field, + float_field, + none_type, + schema_field, + string_field, +) +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Float, Integer + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +OPERATIONS = ["add", "subtract", "multiply", "divide"] + + +class ColumnArithmeticSchema(BaseSchema): + """Schema for ColumnArithmetic hyperparameters.""" + + operation: schema_field( + enum_field(OPERATIONS), + "add", + description=MultilingualString( + en=( + "Arithmetic operation to apply between the selected columns " + "(or between the single selected column and 'constant')." + ), + es=( + "Operación aritmética a aplicar entre las columnas " + "seleccionadas (o entre la única columna seleccionada y " + "'constant')." + ), + pt=( + "Operação aritmética a aplicar entre as colunas selecionadas " + "(ou entre a única coluna selecionada e 'constant')." + ), + de=( + "Arithmetische Operation zwischen den ausgewählten Spalten " + "(oder zwischen der einzelnen ausgewählten Spalte und " + "'constant')." + ), + zh="在所选列之间(或在单个所选列与 'constant' 之间)应用的算术运算。", + ), + ) # type: ignore + constant: schema_field( + none_type(float_field()), + None, + description=MultilingualString( + en=( + "Fixed number used as the second operand. Only used (and " + "required) when a single column is selected." + ), + es=( + "Número fijo usado como segundo operando. Solo se usa (y es " + "requerido) cuando se selecciona una sola columna." + ), + pt=( + "Número fixo usado como segundo operando. Usado (e " + "necessário) apenas quando uma única coluna é selecionada." + ), + de=( + "Feste Zahl, die als zweiter Operand verwendet wird. Wird nur " + "verwendet (und benötigt), wenn eine einzelne Spalte " + "ausgewählt ist." + ), + zh="用作第二个操作数的固定数值。仅在选择单个列时使用(且必填)。", + ), + ) # type: ignore + swap_operands: schema_field( + bool_field(), + False, + description=MultilingualString( + en=( + "When two columns are selected, swap the operand order. By " + "default the operation is 'first column' OP 'second column' " + "(in dataset order); enable this to compute 'second' OP " + "'first' instead. Only affects subtract and divide." + ), + es=( + "Cuando se seleccionan dos columnas, invierte el orden de los " + "operandos. Por defecto la operación es 'primera columna' OP " + "'segunda columna' (en el orden del dataset); actívalo para " + "calcular 'segunda' OP 'primera'. Solo afecta a restar y " + "dividir." + ), + pt=( + "Quando duas colunas são selecionadas, inverte a ordem dos " + "operandos. Por padrão a operação é 'primeira coluna' OP " + "'segunda coluna' (na ordem do dataset); ative para calcular " + "'segunda' OP 'primeira'. Afeta apenas subtrair e dividir." + ), + de=( + "Wenn zwei Spalten ausgewählt sind, wird die Reihenfolge der " + "Operanden getauscht. Standardmäßig ist die Operation 'erste " + "Spalte' OP 'zweite Spalte' (in Datensatzreihenfolge); " + "aktivieren, um stattdessen 'zweite' OP 'erste' zu berechnen. " + "Betrifft nur Subtraktion und Division." + ), + zh="当选择两列时,交换操作数顺序。默认运算为'第一列' OP '第二列'" + "(按数据集顺序);启用此项则改为计算'第二列' OP '第一列'。" + "仅影响减法和除法。", + ), + ) # type: ignore + output_column_name: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Name of the resulting column. If null, a name is generated " + "from the operands and the operation." + ), + es=( + "Nombre de la columna resultante. Si es nulo, se genera un " + "nombre a partir de los operandos y la operación." + ), + pt=( + "Nome da coluna resultante. Se nulo, um nome é gerado a " + "partir dos operandos e da operação." + ), + de=( + "Name der resultierenden Spalte. Wenn null, wird ein Name " + "aus den Operanden und der Operation generiert." + ), + zh="结果列的名称。如果为空,将根据操作数和运算生成名称。", + ), + ) # type: ignore + + +class ColumnArithmetic(FeatureEngineeringConverter, BaseConverter): + """Combine the selected columns into a new numeric column. + + Applies addition, subtraction, multiplication, or division element-wise + to the columns selected in scope. Select **two** columns to operate + between them, or **one** column to operate between it and a fixed + ``constant`` (e.g. ``column * 2``). The operands are taken from the + selection: with two columns the operation is + `` `` in dataset order, which can be + reversed with ``swap_operands`` (relevant for ``subtract`` and + ``divide``). Division by zero yields ``NaN`` instead of raising an + error. + + The original columns are left untouched; the result is appended as a new + column named ``output_column_name``, or, if not provided, + ``__`` or ``__``. + + The output column is ``Integer`` when both operands are ``Integer`` (a + whole-number ``constant`` counts as ``Integer``), the operation is + ``add``, ``subtract``, or ``multiply`` (all of which stay exact on + integers), and neither operand column has missing values (since a + missing value has no exact integer representation). ``divide`` always + produces a ``Float`` column, since integer division is not exact in + general, and any operation involving a ``Float`` operand, or an + operand column with missing values, also produces a ``Float`` column. + """ + + SCHEMA = ColumnArithmeticSchema + DESCRIPTION = MultilingualString( + en=( + "Applies an arithmetic operation (add, subtract, multiply, divide) " + "to the selected columns — two columns to operate between them, or " + "one column with a fixed constant (e.g. column * 2) — and appends " + "the result as a new column." + ), + es=( + "Aplica una operación aritmética (sumar, restar, multiplicar, " + "dividir) a las columnas seleccionadas — dos columnas para operar " + "entre ellas, o una columna con una constante fija (por ejemplo, " + "columna * 2) — y agrega el resultado como una nueva columna." + ), + pt=( + "Aplica uma operação aritmética (somar, subtrair, multiplicar, " + "dividir) às colunas selecionadas — duas colunas para operar " + "entre elas, ou uma coluna com uma constante fixa (por exemplo, " + "coluna * 2) — e adiciona o resultado como uma nova coluna." + ), + de=( + "Wendet eine arithmetische Operation (Addieren, Subtrahieren, " + "Multiplizieren, Dividieren) auf die ausgewählten Spalten an — " + "zwei Spalten, um zwischen ihnen zu rechnen, oder eine Spalte mit " + "einer festen Konstante (z. B. Spalte * 2) — und fügt das " + "Ergebnis als neue Spalte hinzu." + ), + zh=( + "对所选列应用算术运算(加、减、乘、除)——选择两列在其之间运算," + "或选择一列与固定常量运算(例如 列 * 2)——并将结果作为新列追加。" + ), + ) + SHORT_DESCRIPTION = MultilingualString( + en="Arithmetic combination of the selected columns (or a column and a " + "constant).", + es="Combinación aritmética de las columnas seleccionadas (o una " + "columna y una constante).", + pt="Combinação aritmética das colunas selecionadas (ou uma coluna e " + "uma constante).", + de="Arithmetische Kombination der ausgewählten Spalten (oder einer " + "Spalte und einer Konstante).", + zh="对所选列进行算术组合(或一列与一个常量)。", + ) + DISPLAY_NAME = MultilingualString( + en="Column Arithmetic", + es="Aritmética de Columnas", + pt="Aritmética de Colunas", + de="Spalten-Arithmetik", + zh="列算术运算", + ) + IMAGE_PREVIEW = "column_arithmetic.png" + + metadata = { + "allowed_types": [Float, Integer], + "allowed_dtypes": [], + "input_cardinality": {"min": 1, "max": 2}, + } + + def __init__( + self, + operation: str, + constant: Union[float, None] = None, + swap_operands: bool = False, + output_column_name: Union[str, None] = None, + ): + """Initialise the converter with the operation and options. + + The operand columns are not passed here: they are derived from the + columns selected in scope during :meth:`fit` (one or two columns). + + Parameters + ---------- + operation : str + One of ``"add"``, ``"subtract"``, ``"multiply"``, ``"divide"``. + constant : float, optional + Fixed number used as the second operand when a single column is + selected. Ignored when two columns are selected. + swap_operands : bool, optional + When two columns are selected, swap the operand order (compute + ``second first`` instead of ``first second``). Only + relevant for ``subtract`` and ``divide``. Defaults to False. + output_column_name : str, optional + Name of the resulting column. If ``None`` or not a string, a name + is generated from the operands and the operation. + + Raises + ------ + ValueError + If ``operation`` is not one of the supported values. + """ + super().__init__() + if operation not in OPERATIONS: + raise ValueError( + f"'operation' must be one of {OPERATIONS}, got '{operation}'." + ) + + self.operation = operation + self.constant = constant + self.swap_operands = bool(swap_operands) + self.output_column_name = ( + output_column_name if isinstance(output_column_name, str) else None + ) + # Derived during fit() from the columns selected in scope. + self.operand_b_mode: Union[str, None] = None + self.column_a: Union[str, None] = None + self.column_b: Union[str, None] = None + self._result_column_name: Union[str, None] = None + self._output_is_integer: bool = False + + @staticmethod + def _format_constant(value: float) -> str: + """Render a constant for use in an auto-generated column name.""" + return str(int(value)) if value == int(value) else str(value) + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "ColumnArithmetic": + """Derive the operands from scope and validate them. + + The operand columns come from the columns selected in scope: two + columns operate between them (``column_a`` and ``column_b`` in + dataset order, optionally swapped via ``swap_operands``), one column + operates against ``constant``. + + Parameters + ---------- + x : DashAIDataset + The scoped dataset, expected to contain one or two columns. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + ColumnArithmetic + The fitted converter instance (self). + + Raises + ------ + ValueError + If the number of selected columns is not one or two, if a + selected column is not numeric (Float or Integer), or if a + single column is selected without a valid ``constant``. + """ + columns = list(x.column_names) + if len(columns) not in (1, 2): + raise ValueError( + "ColumnArithmetic requires selecting one or two columns in " + f"scope, but {len(columns)} were selected." + ) + + for col in columns: + if not isinstance(x.types.get(col), (Float, Integer)): + raise ValueError( + f"Column '{col}' must be numeric (Float or Integer) to be " + "used in ColumnArithmetic." + ) + + if len(columns) == 2: + self.operand_b_mode = "column" + first, second = columns + if self.swap_operands: + first, second = second, first + self.column_a = first + self.column_b = second + operand_b_label = self.column_b + operand_b_is_integer = isinstance(x.types.get(self.column_b), Integer) + has_missing_values = ( + x.arrow_table[self.column_a].null_count > 0 + or x.arrow_table[self.column_b].null_count > 0 + ) + else: + if not isinstance(self.constant, (int, float)) or isinstance( + self.constant, bool + ): + raise ValueError( + "'constant' must be a number when a single column is " + "selected in scope." + ) + if not math.isfinite(self.constant): + raise ValueError( + "'constant' must be a finite number (not NaN or " + "infinite) when a single column is selected in scope." + ) + self.operand_b_mode = "constant" + self.constant = float(self.constant) + self.column_a = columns[0] + self.column_b = None + operand_b_label = self._format_constant(self.constant) + operand_b_is_integer = self.constant == int(self.constant) + has_missing_values = x.arrow_table[self.column_a].null_count > 0 + + self._result_column_name = self.output_column_name or ( + f"{self.column_a}_{self.operation}_{operand_b_label}" + ) + self._output_is_integer = ( + self.operation != "divide" + and isinstance(x.types.get(self.column_a), Integer) + and operand_b_is_integer + and not has_missing_values + ) + return self + + def _get_operand_b(self, x_pandas, dtype: str): + """Return the second operand as an array aligned with ``x_pandas``. + + Either the values of ``column_b``, or ``constant`` broadcast to the + same length, depending on ``operand_b_mode``. + """ + import numpy as np + + if self.operand_b_mode == "column": + return x_pandas[self.column_b].to_numpy(dtype=dtype) + return np.full(len(x_pandas), self.constant, dtype=dtype) + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Compute the arithmetic result and append it as a new column. + + Parameters + ---------- + x : DashAIDataset + The dataset containing the operand column(s) derived during + ``fit``. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The original dataset with the arithmetic result appended as a + new column, typed ``Integer`` or ``Float`` depending on the + operands and the operation (see class docstring). + """ + import numpy as np + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + + x_pandas = x.to_pandas() + + if self._output_is_integer: + a = x_pandas[self.column_a].to_numpy(dtype="int64") + b = self._get_operand_b(x_pandas, "int64") + + if self.operation == "add": + result = a + b + elif self.operation == "subtract": + result = a - b + else: # multiply + result = a * b + + arrow_type = pa.int64() + else: + a = x_pandas[self.column_a].to_numpy(dtype="float64") + b = self._get_operand_b(x_pandas, "float64") + + with np.errstate(divide="ignore", invalid="ignore"): + if self.operation == "add": + result = a + b + elif self.operation == "subtract": + result = a - b + elif self.operation == "multiply": + result = a * b + else: # divide + result = np.where(b != 0, a / b, np.nan) + + arrow_type = pa.float64() + + new_types = dict(x.types) + new_types[self._result_column_name] = self.get_output_type() + + return modify_table( + x, + {self._result_column_name: pa.array(result, type=arrow_type)}, + types=new_types, + ) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type for the arithmetic result. + + Determined during ``fit``: ``Integer`` when both operands are + ``Integer`` (a whole-number ``constant`` counts as ``Integer``) and + the operation isn't ``divide``, ``Float`` otherwise. + + Parameters + ---------- + column_name : str, optional + Not used; the result column always has the same type. + Defaults to None. + + Returns + ------- + DashAIDataType + An ``Integer`` type backed by ``pyarrow.int64()``, or a + ``Float`` type backed by ``pyarrow.float64()``. + """ + import pyarrow as pa + + if self._output_is_integer: + return Integer(arrow_type=pa.int64()) + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/simple_converters/column_concat.py b/DashAI/back/converters/simple_converters/column_concat.py new file mode 100644 index 000000000..9027a685b --- /dev/null +++ b/DashAI/back/converters/simple_converters/column_concat.py @@ -0,0 +1,371 @@ +from typing import TYPE_CHECKING, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.feature_engineering import ( + FeatureEngineeringConverter, +) +from DashAI.back.core.schema_fields import ( + bool_field, + none_type, + schema_field, + string_field, +) +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Text + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +class ColumnConcatSchema(BaseSchema): + """Schema for ColumnConcat hyperparameters.""" + + constant: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Fixed string used as the second operand. Only used (and " + "required) when a single column is selected." + ), + es=( + "String fijo usado como segundo operando. Solo se usa (y es " + "requerido) cuando se selecciona una sola columna." + ), + pt=( + "String fixa usada como segundo operando. Usada (e " + "necessária) apenas quando uma única coluna é selecionada." + ), + de=( + "Feste Zeichenkette, die als zweiter Operand verwendet wird. " + "Wird nur verwendet (und benötigt), wenn eine einzelne Spalte " + "ausgewählt ist." + ), + zh="用作第二个操作数的固定字符串。仅在选择单个列时使用(且必填)。", + ), + ) # type: ignore + separator: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Optional string inserted between the two operands. If null, " + "they are joined directly with no separator." + ), + es=( + "String opcional insertado entre los dos operandos. Si es " + "nulo, se unen directamente sin separador." + ), + pt=( + "String opcional inserida entre os dois operandos. Se nula, " + "são unidos diretamente sem separador." + ), + de=( + "Optionale Zeichenkette, die zwischen die beiden Operanden " + "eingefügt wird. Wenn null, werden sie direkt ohne " + "Trennzeichen verbunden." + ), + zh="插入在两个操作数之间的可选字符串。如果为空,则直接连接,无分隔符。", + ), + ) # type: ignore + swap_operands: schema_field( + bool_field(), + False, + description=MultilingualString( + en=( + "When two columns are selected, swap the concatenation order. " + "By default it is 'first column' + 'second column' (in " + "dataset order); enable this to concatenate 'second' + " + "'first' instead." + ), + es=( + "Cuando se seleccionan dos columnas, invierte el orden de " + "concatenación. Por defecto es 'primera columna' + 'segunda " + "columna' (en el orden del dataset); actívalo para concatenar " + "'segunda' + 'primera'." + ), + pt=( + "Quando duas colunas são selecionadas, inverte a ordem de " + "concatenação. Por padrão é 'primeira coluna' + 'segunda " + "coluna' (na ordem do dataset); ative para concatenar " + "'segunda' + 'primeira'." + ), + de=( + "Wenn zwei Spalten ausgewählt sind, wird die " + "Verkettungsreihenfolge getauscht. Standardmäßig ist es " + "'erste Spalte' + 'zweite Spalte' (in Datensatzreihenfolge); " + "aktivieren, um stattdessen 'zweite' + 'erste' zu verketten." + ), + zh="当选择两列时,交换连接顺序。默认为'第一列' + '第二列'" + "(按数据集顺序);启用此项则改为连接'第二列' + '第一列'。", + ), + ) # type: ignore + output_column_name: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Name of the resulting column. If null, a name is generated " + "from the operands." + ), + es=( + "Nombre de la columna resultante. Si es nulo, se genera un " + "nombre a partir de los operandos." + ), + pt=( + "Nome da coluna resultante. Se nulo, um nome é gerado a " + "partir dos operandos." + ), + de=( + "Name der resultierenden Spalte. Wenn null, wird ein Name " + "aus den Operanden generiert." + ), + zh="结果列的名称。如果为空,将根据操作数生成名称。", + ), + ) # type: ignore + + +class ColumnConcat(FeatureEngineeringConverter, BaseConverter): + """Concatenate the selected string columns into a new column. + + Joins the columns selected in scope element-wise. Select **two** columns + to concatenate them together, or **one** column to concatenate it with a + fixed ``constant`` string. With two columns the order is + `` + `` in dataset order, which can be + reversed with ``swap_operands``. An optional ``separator`` is inserted + between the operands (none by default, e.g. ``"foo"`` + ``"bar"`` -> + ``"foobar"``). All selected columns must be ``Text`` or ``Categorical``. + If either operand is missing (``None``) for a row, the result for that + row is ``None``. + + The original columns are left untouched; the result is appended as a + new ``Text`` column named ``output_column_name``, or, if not provided, + ``_concat_`` or ``_concat_``. + """ + + SCHEMA = ColumnConcatSchema + DESCRIPTION = MultilingualString( + en=( + "Concatenates the selected string columns — two columns to join " + "them, or one column with a fixed string — with an optional " + "separator, and appends the result as a new column." + ), + es=( + "Concatena las columnas de texto seleccionadas — dos columnas " + "para unirlas, o una columna con un string fijo — con un " + "separador opcional, y agrega el resultado como una nueva " + "columna." + ), + pt=( + "Concatena as colunas de texto selecionadas — duas colunas para " + "uni-las, ou uma coluna com uma string fixa — com um separador " + "opcional, e adiciona o resultado como uma nova coluna." + ), + de=( + "Verkettet die ausgewählten Textspalten — zwei Spalten, um sie " + "zu verbinden, oder eine Spalte mit einer festen Zeichenkette — " + "mit einem optionalen Trennzeichen und fügt das Ergebnis als " + "neue Spalte hinzu." + ), + zh="连接所选的文本列——两列将其合并,或一列与固定字符串——带可选分隔符," + "并将结果作为新列追加。", + ) + SHORT_DESCRIPTION = MultilingualString( + en="Concatenates the selected string columns (or a column and a constant).", + es="Concatena las columnas de texto seleccionadas (o una columna y " + "una constante).", + pt="Concatena as colunas de texto selecionadas (ou uma coluna e uma " + "constante).", + de="Verkettet die ausgewählten Textspalten (oder eine Spalte und " + "eine Konstante).", + zh="连接所选的文本列(或一列与一个常量)。", + ) + DISPLAY_NAME = MultilingualString( + en="Column Concat", + es="Concatenación de Columnas", + pt="Concatenação de Colunas", + de="Spalten-Verkettung", + zh="列拼接", + ) + IMAGE_PREVIEW = "column_concat.png" + + metadata = { + "allowed_types": [Text, Categorical], + "allowed_dtypes": [], + "input_cardinality": {"min": 1, "max": 2}, + } + + def __init__( + self, + constant: Union[str, None] = None, + separator: Union[str, None] = None, + swap_operands: bool = False, + output_column_name: Union[str, None] = None, + ): + """Initialise the converter with the concatenation options. + + The operand columns are not passed here: they are derived from the + columns selected in scope during :meth:`fit` (one or two columns). + + Parameters + ---------- + constant : str, optional + Fixed string used as the second operand when a single column is + selected. Ignored when two columns are selected. + separator : str, optional + String inserted between the two operands. Defaults to None (no + separator). + swap_operands : bool, optional + When two columns are selected, swap the concatenation order + (``second + first`` instead of ``first + second``). Defaults to + False. + output_column_name : str, optional + Name of the resulting column. If ``None`` or not a string, a name + is generated from the operands. + """ + super().__init__() + self.constant = constant + self.separator = separator if isinstance(separator, str) else "" + self.swap_operands = bool(swap_operands) + self.output_column_name = ( + output_column_name if isinstance(output_column_name, str) else None + ) + # Derived during fit() from the columns selected in scope. + self.operand_b_mode: Union[str, None] = None + self.column_a: Union[str, None] = None + self.column_b: Union[str, None] = None + self._result_column_name: Union[str, None] = None + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "ColumnConcat": + """Derive the operands from scope and validate them. + + The operand columns come from the columns selected in scope: two + columns concatenate together (``column_a`` and ``column_b`` in + dataset order, optionally swapped via ``swap_operands``), one column + concatenates with ``constant``. + + Parameters + ---------- + x : DashAIDataset + The scoped dataset, expected to contain one or two columns. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + ColumnConcat + The fitted converter instance (self). + + Raises + ------ + ValueError + If the number of selected columns is not one or two, if a + selected column is not string-like (Text or Categorical), or if + a single column is selected without a valid ``constant``. + """ + columns = list(x.column_names) + if len(columns) not in (1, 2): + raise ValueError( + "ColumnConcat requires selecting one or two columns in " + f"scope, but {len(columns)} were selected." + ) + + for col in columns: + if not isinstance(x.types.get(col), (Text, Categorical)): + raise ValueError( + f"Column '{col}' must be a string (Text or Categorical) " + "to be used in ColumnConcat." + ) + + if len(columns) == 2: + self.operand_b_mode = "column" + first, second = columns + if self.swap_operands: + first, second = second, first + self.column_a = first + self.column_b = second + operand_b_label = self.column_b + else: + if not isinstance(self.constant, str): + raise ValueError( + "'constant' must be a string when a single column is " + "selected in scope." + ) + self.operand_b_mode = "constant" + self.column_a = columns[0] + self.column_b = None + operand_b_label = self.constant + + self._result_column_name = self.output_column_name or ( + f"{self.column_a}_concat_{operand_b_label}" + ) + return self + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Concatenate the operands and append the result as a new column. + + Parameters + ---------- + x : DashAIDataset + The dataset containing the operand column(s) derived during + ``fit``. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The original dataset with the concatenated result appended as a + new ``Text`` column. + """ + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + + x_pandas = x.to_pandas() + + a = x_pandas[self.column_a].tolist() + if self.operand_b_mode == "column": + b = x_pandas[self.column_b].tolist() + else: + b = [self.constant] * len(x_pandas) + + sep = self.separator + result = [ + None if av is None or bv is None else f"{av}{sep}{bv}" + for av, bv in zip(a, b, strict=True) + ] + + new_types = dict(x.types) + new_types[self._result_column_name] = self.get_output_type() + + return modify_table( + x, + {self._result_column_name: pa.array(result, type=pa.string())}, + types=new_types, + ) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type for the concatenation result. + + Parameters + ---------- + column_name : str, optional + Not used; the result column always has the same type. + Defaults to None. + + Returns + ------- + DashAIDataType + A ``Text`` type backed by ``pyarrow.string()``. + """ + import pyarrow as pa + + return Text(arrow_type=pa.string()) diff --git a/DashAI/back/converters/simple_converters/numeric_expansion.py b/DashAI/back/converters/simple_converters/numeric_expansion.py new file mode 100644 index 000000000..4db709263 --- /dev/null +++ b/DashAI/back/converters/simple_converters/numeric_expansion.py @@ -0,0 +1,269 @@ +from typing import TYPE_CHECKING, Dict, List, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.feature_engineering import ( + FeatureEngineeringConverter, +) +from DashAI.back.core.schema_fields import enum_field, schema_field +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Float, Integer + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +OPERATIONS = ["log1p", "square", "sqrt"] + + +class NumericExpansionSchema(BaseSchema): + """Schema for NumericExpansion hyperparameters.""" + + operation: schema_field( + enum_field(OPERATIONS), + "log1p", + description=MultilingualString( + en=( + "Unary numeric expansion to apply to each selected column: " + "'log1p' (ln(1+x)), 'square' (x^2), or 'sqrt' (sqrt(x))." + ), + es=( + "Expansión numérica unaria a aplicar a cada columna " + "seleccionada: 'log1p' (ln(1+x)), 'square' (x^2) o " + "'sqrt' (raíz cuadrada de x)." + ), + pt=( + "Expansão numérica unária a aplicar a cada coluna " + "selecionada: 'log1p' (ln(1+x)), 'square' (x^2) ou " + "'sqrt' (raiz quadrada de x)." + ), + de=( + "Unäre numerische Erweiterung, die auf jede ausgewählte " + "Spalte angewendet wird: 'log1p' (ln(1+x)), 'square' (x^2) " + "oder 'sqrt' (Quadratwurzel von x)." + ), + zh="应用于每个所选列的一元数值扩展:" + "'log1p'(ln(1+x))、'square'(x^2)或 'sqrt'(x 的平方根)。", + ), + ) # type: ignore + + +class NumericExpansion(FeatureEngineeringConverter, BaseConverter): + """Derive a new numeric feature from each selected column via a unary function. + + Applies one of ``log1p`` (``ln(1+x)``), ``square`` (``x^2``), or ``sqrt`` + (``sqrt(x)``) to every numeric column in scope, appending one new column + per input column named ``_``. Values outside the + domain of the chosen function (``x <= -1`` for ``log1p``, ``x < 0`` for + ``sqrt``) become ``NaN`` in the corresponding output. + + The original columns are left untouched. ``square`` preserves the input + column's type (``Integer`` stays ``Integer``, ``Float`` stays ``Float``) + when the source column has no missing values, since squaring is exact + for both. ``log1p`` and ``sqrt`` always produce a ``Float`` column, since + they can yield non-integer or ``NaN`` results even from integer input, + and ``square`` also falls back to ``Float`` when the source ``Integer`` + column has missing values (since a missing value has no exact integer + representation). + """ + + SCHEMA = NumericExpansionSchema + DESCRIPTION = MultilingualString( + en=( + "Applies a unary numeric expansion (log1p, square, or sqrt) to " + "each selected column and appends the result as a new column." + ), + es=( + "Aplica una expansión numérica unaria (log1p, square o sqrt) a " + "cada columna seleccionada y agrega el resultado como una nueva " + "columna." + ), + pt=( + "Aplica uma expansão numérica unária (log1p, square ou sqrt) a " + "cada coluna selecionada e adiciona o resultado como uma nova " + "coluna." + ), + de=( + "Wendet eine unäre numerische Erweiterung (log1p, square oder " + "sqrt) auf jede ausgewählte Spalte an und fügt das Ergebnis als " + "neue Spalte hinzu." + ), + zh=( + "对每个所选列应用一元数值扩展(log1p、square 或 sqrt)," + "并将结果作为新列追加。" + ), + ) + SHORT_DESCRIPTION = MultilingualString( + en="Unary numeric expansion (log1p, square, sqrt) of a column.", + es="Expansión numérica unaria (log1p, square, sqrt) de una columna.", + pt="Expansão numérica unária (log1p, square, sqrt) de uma coluna.", + de="Unäre numerische Erweiterung (log1p, square, sqrt) einer Spalte.", + zh="列的一元数值扩展(log1p、square、sqrt)。", + ) + DISPLAY_NAME = MultilingualString( + en="Numeric Expansion", + es="Expansión Numérica", + pt="Expansão Numérica", + de="Numerische Erweiterung", + zh="数值扩展", + ) + IMAGE_PREVIEW = "numeric_expansion.png" + + metadata = { + "allowed_types": [Float, Integer], + "allowed_dtypes": [], + } + + def __init__(self, operation: str): + """Initialise the converter with the unary operation to apply. + + Parameters + ---------- + operation : str + One of ``"log1p"``, ``"square"``, ``"sqrt"``. + + Raises + ------ + ValueError + If ``operation`` is not one of the supported operations. + """ + super().__init__() + if operation not in OPERATIONS: + raise ValueError( + f"'operation' must be one of {OPERATIONS}, got '{operation}'." + ) + self.operation = operation + self._target_columns: List[str] = [] + self._output_types: Dict[str, DashAIDataType] = {} + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "NumericExpansion": + """Identify which columns in ``x`` are numeric (Float or Integer). + + Also precomputes the output type of each resulting column: ``square`` + keeps the input column's type, while ``log1p`` and ``sqrt`` always + produce ``Float``. + + Parameters + ---------- + x : DashAIDataset + The dataset whose columns will be inspected. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + NumericExpansion + The fitted converter instance (self). + """ + import pyarrow as pa + + self._target_columns = [] + self._output_types = {} + for col_name in x.column_names: + col_type = x.types.get(col_name) + if isinstance(col_type, (Float, Integer)): + self._target_columns.append(col_name) + new_col_name = f"{self.operation}_{col_name}" + has_missing_values = x.arrow_table[col_name].null_count > 0 + if ( + self.operation == "square" + and isinstance(col_type, Integer) + and not has_missing_values + ): + self._output_types[new_col_name] = Integer(arrow_type=pa.int64()) + else: + self._output_types[new_col_name] = Float(arrow_type=pa.float64()) + else: + print( + f"Warning: Column '{col_name}' in scope is not numeric " + "(Float or Integer) and will be ignored by NumericExpansion." + ) + if not self._target_columns: + print( + "Warning: NumericExpansion did not find any valid numeric " + "columns in the provided scope." + ) + return self + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Apply the configured unary expansion to the fitted numeric columns. + + Parameters + ---------- + x : DashAIDataset + The dataset to transform. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The dataset with one new ``_`` column appended + per fitted numeric column, typed ``Integer`` or ``Float`` + depending on the source column and the operation (see class + docstring). + """ + import numpy as np + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + + if not self._target_columns: + return x + + x_pandas = x.to_pandas() + new_columns = {} + new_types = dict(x.types) + + with np.errstate(divide="ignore", invalid="ignore"): + for col in self._target_columns: + new_col_name = f"{self.operation}_{col}" + output_type = self._output_types[new_col_name] + + if isinstance(output_type, Integer): + values = x_pandas[col].to_numpy(dtype="int64") + result = values**2 + arrow_type = pa.int64() + else: + values = x_pandas[col].to_numpy(dtype="float64") + if self.operation == "log1p": + result = np.where(values > -1, np.log1p(values), np.nan) + elif self.operation == "square": + result = values**2 + else: # sqrt + result = np.where(values >= 0, np.sqrt(values), np.nan) + arrow_type = pa.float64() + + new_columns[new_col_name] = pa.array(result, type=arrow_type) + new_types[new_col_name] = output_type + + return modify_table(x, new_columns, types=new_types) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type for a given expanded column. + + Determined during ``fit``: ``Integer`` when the operation is + ``square`` and the source column is ``Integer``, ``Float`` + otherwise. + + Parameters + ---------- + column_name : str, optional + Name of the output column (e.g. ``"square_age"``). Defaults to + None. + + Returns + ------- + DashAIDataType + An ``Integer`` type backed by ``pyarrow.int64()``, or a + ``Float`` type backed by ``pyarrow.float64()``. + """ + import pyarrow as pa + + if column_name in self._output_types: + return self._output_types[column_name] + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/simple_converters/type_cast.py b/DashAI/back/converters/simple_converters/type_cast.py new file mode 100644 index 000000000..38c5877f0 --- /dev/null +++ b/DashAI/back/converters/simple_converters/type_cast.py @@ -0,0 +1,381 @@ +from typing import TYPE_CHECKING, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.basic_preprocessing import ( + BasicPreprocessingConverter, +) +from DashAI.back.core.schema_fields import enum_field, schema_field +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Float, Integer, Text + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +NEW_TYPES = ["Integer", "Float", "Text", "Categorical"] +ON_ERROR_MODES = ["raise", "skip"] + + +class TypeCastSchema(BaseSchema): + """Schema for TypeCast hyperparameters.""" + + new_type: schema_field( + enum_field(NEW_TYPES), + "Text", + description=MultilingualString( + en="Target type to cast the columns in scope to.", + es="Tipo objetivo al que se convertirán las columnas del alcance.", + pt="Tipo de destino para o qual as colunas do escopo serão convertidas.", + de="Zieltyp, in den die Spalten im Geltungsbereich umgewandelt werden.", + zh="要将范围内的列转换为的目标类型。", + ), + ) # type: ignore + on_error: schema_field( + enum_field(ON_ERROR_MODES), + "raise", + description=MultilingualString( + en=( + "What to do when a column cannot be safely converted: 'raise' " + "to stop with a descriptive error, or 'skip' to leave that " + "column unchanged and continue with the rest." + ), + es=( + "Qué hacer cuando una columna no puede convertirse de forma " + "segura: 'raise' para detenerse con un error descriptivo, o " + "'skip' para dejar esa columna sin cambios y continuar con " + "el resto." + ), + pt=( + "O que fazer quando uma coluna não pode ser convertida com " + "segurança: 'raise' para parar com um erro descritivo, ou " + "'skip' para deixar essa coluna inalterada e continuar com " + "o restante." + ), + de=( + "Was zu tun ist, wenn eine Spalte nicht sicher konvertiert " + "werden kann: 'raise', um mit einer aussagekräftigen " + "Fehlermeldung abzubrechen, oder 'skip', um diese Spalte " + "unverändert zu lassen und mit dem Rest fortzufahren." + ), + zh="当某列无法安全转换时的处理方式:'raise' 以详细错误信息终止," + "或 'skip' 保持该列不变并继续处理其余列。", + ), + ) # type: ignore + + +class TypeCast(BasicPreprocessingConverter, BaseConverter): + """Change the DashAI type of the columns selected in scope. + + Casts every column in scope to ``new_type`` (one of ``"Integer"``, + ``"Float"``, ``"Text"``, or ``"Categorical"``), reusing the exact same + validation and conversion rules used when changing column types from + the dataset upload preview screen (see + ``DashAI.back.types.type_validation.validate_type_change``, also used by + the ``/datasets/validate_type_changes`` endpoint). This keeps behaviour + consistent between the upload preview and pipeline-time type changes. + + Columns already of ``new_type`` are left untouched. If a column's + values cannot be safely converted (e.g. a ``Text`` column with + non-numeric values targeting ``Integer``, or a ``Float`` column with + decimal values targeting ``Integer``), the behaviour is controlled by + ``on_error``: ``"raise"`` (default) stops with a descriptive, + column-specific error message, while ``"skip"`` leaves that column + unchanged, prints a warning, and continues with the rest. + """ + + SCHEMA = TypeCastSchema + DESCRIPTION = MultilingualString( + en=( + "Changes the type of the selected columns (Integer, Float, Text, " + "or Categorical), using the same validation used in the dataset " + "upload preview. Columns that cannot be safely converted are " + "either reported as an error or skipped, depending on 'on_error'." + ), + es=( + "Cambia el tipo de las columnas seleccionadas (Integer, Float, " + "Text o Categorical), usando la misma validación empleada en la " + "vista previa de carga del dataset. Las columnas que no pueden " + "convertirse de forma segura se reportan como error o se omiten, " + "según 'on_error'." + ), + pt=( + "Altera o tipo das colunas selecionadas (Integer, Float, Text ou " + "Categorical), usando a mesma validação empregada na pré-" + "visualização de upload do dataset. Colunas que não podem ser " + "convertidas com segurança são reportadas como erro ou " + "ignoradas, dependendo de 'on_error'." + ), + de=( + "Ändert den Typ der ausgewählten Spalten (Integer, Float, Text " + "oder Categorical) und verwendet dabei dieselbe Validierung wie " + "in der Upload-Vorschau des Datensatzes. Spalten, die nicht " + "sicher konvertiert werden können, werden je nach 'on_error' " + "entweder als Fehler gemeldet oder übersprungen." + ), + zh="使用与数据集上传预览相同的验证规则,更改所选列的类型(Integer、" + "Float、Text 或 Categorical)。无法安全转换的列将根据 'on_error' " + "报告为错误或被跳过。", + ) + SHORT_DESCRIPTION = MultilingualString( + en="Changes the type of the columns in scope.", + es="Cambia el tipo de las columnas del alcance.", + pt="Altera o tipo das colunas do escopo.", + de="Ändert den Typ der Spalten im Geltungsbereich.", + zh="更改范围内列的类型。", + ) + DISPLAY_NAME = MultilingualString( + en="Type Cast", + es="Cambio de Tipo", + pt="Conversão de Tipo", + de="Typumwandlung", + zh="类型转换", + ) + IMAGE_PREVIEW = "type_cast.png" + + metadata = { + "allowed_types": [Integer, Float, Text, Categorical], + "allowed_dtypes": [], + } + + def __init__(self, new_type: str, on_error: str = "raise"): + """Initialise the converter with the target type and error behaviour. + + Parameters + ---------- + new_type : str + One of ``"Integer"``, ``"Float"``, ``"Text"``, ``"Categorical"``. + on_error : str, optional + Either ``"raise"`` (default), to stop on the first column that + cannot be converted, or ``"skip"``, to leave it unchanged and + continue with the rest. + + Raises + ------ + ValueError + If ``new_type`` or ``on_error`` is not one of the supported + values. + """ + super().__init__() + if new_type not in NEW_TYPES: + raise ValueError( + f"'new_type' must be one of {NEW_TYPES}, got '{new_type}'." + ) + if on_error not in ON_ERROR_MODES: + raise ValueError( + f"'on_error' must be one of {ON_ERROR_MODES}, got '{on_error}'." + ) + + self.new_type = new_type + self.on_error = on_error + self._target_columns: list = [] + self._current_types: dict = {} + self._skip_columns: set = set() + # Cache of already-converted columns from fit(), reused in transform() + # when it is called with the same dataset (the common case, since + # ConverterJob only re-fetches a fresh scope when a row scope is set). + self._fit_x = None + self._converted_cache: dict = {} + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "TypeCast": + """Validate that every column in scope can be cast to ``new_type``. + + Parameters + ---------- + x : DashAIDataset + The scoped dataset whose columns will be cast. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + TypeCast + The fitted converter instance (self). + + Raises + ------ + ValueError + If ``on_error`` is ``"raise"`` and a column's values cannot be + safely converted to ``new_type``. + """ + from DashAI.back.types.type_validation import validate_type_change + + self._target_columns = list(x.column_names) + self._current_types = {} + self._skip_columns = set() + self._converted_cache = {} + self._fit_x = x + + if not self._target_columns: + return self + + x_pandas = x.to_pandas() + for col in self._target_columns: + current_type_obj = x.types.get(col) + if current_type_obj is None: + print( + f"Warning: column '{col}' has no known DashAI type and " + "will be left unchanged by TypeCast." + ) + self._skip_columns.add(col) + continue + + current_type_str = current_type_obj.to_string().get("type") + self._current_types[col] = current_type_str + + if current_type_str == self.new_type: + continue + + is_valid, message, converted = validate_type_change( + x_pandas[col], current_type_str, self.new_type + ) + if not is_valid: + full_message = ( + f"Column '{col}' cannot be converted from " + f"'{current_type_str}' to '{self.new_type}': {message}" + ) + if self.on_error == "raise": + raise ValueError(full_message) + print(f"Warning: {full_message} The column will be left unchanged.") + self._skip_columns.add(col) + continue + + if message: + print(f"Warning: column '{col}': {message}") + self._converted_cache[col] = converted + + return self + + def _arrow_type(self): + """Return the PyArrow type backing ``new_type``.""" + import pyarrow as pa + + return { + "Integer": pa.int64(), + "Float": pa.float64(), + "Text": pa.string(), + "Categorical": pa.string(), + }[self.new_type] + + def _cast_values(self, values: list) -> list: + """Cast a list of non-null-normalised Python values to ``new_type``.""" + if self.new_type == "Integer": + return [None if v is None else int(v) for v in values] + if self.new_type == "Float": + return [None if v is None else float(v) for v in values] + return [None if v is None else str(v) for v in values] + + def _build_output_type(self, values: list) -> DashAIDataType: + """Return the DashAI type for a converted column's values.""" + from DashAI.back.types.utils import arrow_to_dashai_types + + if self.new_type != "Categorical": + return arrow_to_dashai_types(self._arrow_type()) + unique_values = sorted({v for v in values if v is not None}) + return Categorical(values=unique_values) + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Cast the fitted columns to ``new_type``. + + Parameters + ---------- + x : DashAIDataset + The dataset whose columns (matching those seen in ``fit``) will + be cast. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The dataset with the fitted columns cast to ``new_type``. + Columns that already had ``new_type``, that had no known type, + or that failed conversion under ``on_error="skip"`` are left + unchanged. + + Raises + ------ + ValueError + If ``on_error`` is ``"raise"`` and a column's values cannot be + safely converted to ``new_type``. + """ + import pandas as pd + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + from DashAI.back.types.type_validation import validate_type_change + + if not self._target_columns: + return x + + x_pandas = x.to_pandas() + new_columns = {} + new_types = dict(x.types) + arrow_type = self._arrow_type() + reuse_cache = x is self._fit_x + + for col in self._target_columns: + if col in self._skip_columns: + continue + current_type_str = self._current_types.get(col) + if current_type_str == self.new_type: + continue + + if reuse_cache and col in self._converted_cache: + converted = self._converted_cache[col] + else: + is_valid, message, converted = validate_type_change( + x_pandas[col], current_type_str, self.new_type + ) + if not is_valid: + full_message = ( + f"Column '{col}' cannot be converted from " + f"'{current_type_str}' to '{self.new_type}': {message}" + ) + if self.on_error == "raise": + raise ValueError(full_message) + print(f"Warning: {full_message} The column will be left unchanged.") + continue + + clean_values = [ + None if pd.isna(v) else v for v in converted.reindex(x_pandas.index) + ] + cast_values = self._cast_values(clean_values) + new_columns[col] = pa.array(cast_values, type=arrow_type) + new_types[col] = self._build_output_type(cast_values) + + if not new_columns: + return x + + return modify_table(x, new_columns, types=new_types) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type produced for any column cast by this converter. + + Every column cast by ``transform`` ends up with the same type, + ``new_type``, regardless of its original type. + + Parameters + ---------- + column_name : str, optional + Not used; every cast column has the same output type. + Defaults to None. + + Returns + ------- + DashAIDataType + An ``Integer``, ``Float``, ``Text``, or ``Categorical`` type + matching ``new_type``. For ``Categorical``, the categories are + unknown until ``transform`` runs, so an empty placeholder is + returned. + """ + from DashAI.back.types.utils import arrow_to_dashai_types + + if self.new_type != "Categorical": + return arrow_to_dashai_types(self._arrow_type()) + return Categorical(values=[]) diff --git a/DashAI/back/converters/sklearn_wrapper.py b/DashAI/back/converters/sklearn_wrapper.py index 8879687d8..5bbfd0b2b 100644 --- a/DashAI/back/converters/sklearn_wrapper.py +++ b/DashAI/back/converters/sklearn_wrapper.py @@ -10,6 +10,39 @@ from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset +def _normalize_missing(df: object) -> object: + """Normalize missing-value sentinels to ``np.nan`` before handing off to sklearn. + + ``DashAIDataset.to_pandas()`` converts Arrow nulls in string/Categorical + columns to Python ``None``, while numeric columns get actual float + ``nan``. Several sklearn transformers (notably ``SimpleImputer``, whose + default ``missing_values=np.nan`` masks via a ``value != value`` check) + only recognize float ``nan`` as missing: ``None != None`` is ``False``, + so ``None`` entries are silently treated as real values and never + imputed, even though ``pandas``/Arrow both consider them missing. + ``DataFrame.notna()`` correctly flags both, so this unifies the + representation without altering any non-missing value or dtype. + + Parameters + ---------- + df : object + The value returned by ``DashAIDataset.to_pandas()``. Only + ``pandas.DataFrame`` instances are normalized; anything else is + returned unchanged. + + Returns + ------- + object + The normalized DataFrame, or ``df`` unchanged if it isn't one. + """ + import numpy as np + import pandas as pd + + if not isinstance(df, pd.DataFrame): + return df + return df.where(df.notna(), np.nan) + + class SklearnWrapper(BaseConverter, metaclass=ABCMeta): """Abstract mixin that adapts scikit-learn transformers to the DashAI converter API. @@ -85,49 +118,60 @@ def fit( If no scikit-learn class with a `fit` method is found in the MRO. """ - x_pandas = x.to_pandas() if hasattr(x, "to_pandas") else x - y_pandas = y.to_pandas() if y is not None and hasattr(y, "to_pandas") else y - - # Detect whether the underlying sklearn estimator needs a target. - # sklearn >= 1.6 moved tags from ``_get_tags`` to ``__sklearn_tags__``, - # so we consult both for backward/forward compatibility. - requires_y = False - if hasattr(self, "__sklearn_tags__"): - try: - tags = self.__sklearn_tags__() - target_tags = getattr(tags, "target_tags", None) - if target_tags is not None: - requires_y = bool(getattr(target_tags, "required", False)) - except Exception: - requires_y = False - if not requires_y and hasattr(self, "_get_tags"): - with contextlib.suppress(Exception): - requires_y = bool(self._get_tags().get("requires_y", False)) - - if requires_y and y is None: - raise ValueError("This transformer requires y for fitting") - - sklearn_cls = next( - ( - cls - for cls in type(self).__mro__ - if "sklearn" in cls.__module__ - and "DashAI" not in cls.__module__ - and "fit" in cls.__dict__ - ), - None, - ) + try: + if hasattr(x, "to_pandas"): + x_pandas = _normalize_missing(x.to_pandas()) + self._fit_input_cache = (x, x_pandas) + else: + x_pandas = x + self._fit_input_cache = None + y_pandas = ( + _normalize_missing(y.to_pandas()) + if y is not None and hasattr(y, "to_pandas") + else y + ) - if sklearn_cls is None: - raise RuntimeError( - "No sklearn class with a 'fit' method found in the MRO. " - "Ensure that your transformer inherits from a valid sklearn class." + requires_y = False + if hasattr(self, "__sklearn_tags__"): + try: + tags = self.__sklearn_tags__() + target_tags = getattr(tags, "target_tags", None) + if target_tags is not None: + requires_y = bool(getattr(target_tags, "required", False)) + except Exception: + requires_y = False + if not requires_y and hasattr(self, "_get_tags"): + with contextlib.suppress(Exception): + requires_y = bool(self._get_tags().get("requires_y", False)) + + if requires_y and y is None: + raise ValueError("This transformer requires y for fitting") + + sklearn_cls = next( + ( + cls + for cls in type(self).__mro__ + if "sklearn" in cls.__module__ + and "DashAI" not in cls.__module__ + and "fit" in cls.__dict__ + ), + None, ) - fit_method = sklearn_cls.__dict__["fit"] - if requires_y or y_pandas is not None: - fit_method(self, x_pandas, y_pandas) - else: - fit_method(self, x_pandas) + + if sklearn_cls is None: + raise RuntimeError( + "No sklearn class with a 'fit' method found in the MRO. " + "Ensure that your transformer inherits from a valid sklearn " + "class." + ) + fit_method = sklearn_cls.__dict__["fit"] + if requires_y or y_pandas is not None: + fit_method(self, x_pandas, y_pandas) + else: + fit_method(self, x_pandas) + except Exception: + self._fit_input_cache = None + raise return self @@ -165,7 +209,16 @@ def transform( from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset - x_pandas = x.to_pandas() if hasattr(x, "to_pandas") else x + cached = getattr(self, "_fit_input_cache", None) + if cached is not None and cached[0] is x: + x_pandas = cached[1] + elif hasattr(x, "to_pandas"): + x_pandas = _normalize_missing(x.to_pandas()) + else: + x_pandas = x + # Drop the reference now that it's been consumed (or wasn't a hit), + # so the cached DataFrame doesn't outlive this transform call. + self._fit_input_cache = None sklearn_cls = next( ( diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py new file mode 100644 index 000000000..04d423d49 --- /dev/null +++ b/DashAI/back/core/artifacts.py @@ -0,0 +1,556 @@ +"""Typed render artifacts shared by explainers and explorers. + +An artifact is the unit of renderable output that a component (an explainer's +``plot`` method, an explorer's ``get_results`` method) hands to the frontend. +Every artifact serializes to the wire format:: + + {"type": , "payload": , "title": } + +Supported types: + +- ``"plotly"``: payload is a JSON string produced by ``plotly.io.to_json``. +- ``"table"``: payload is ``{"columns": List[str], "rows": List[List[Any]], + "highlight": List[{"row": int, "column": int}]}``. +- ``"text"``: payload is a plain string rendered as preformatted text. +- ``"image"``: payload is ``{"data": , "mime": }``. + +A component may also return a :class:`GroupedArtifacts` alongside plain +artifacts. It bundles several :class:`ArtifactGroup` entries (each a titled +batch of leaf artifacts, e.g. a summary table next to its plot) into one +interactive selector: the frontend lists every group's title and shows one +group's artifacts at a time. It serializes to ``{"type": "grouped", "title": +, "groups": [{"title": , "artifacts": [, ...]}, ...]}``; a group cannot itself contain another group. + +Components created before this module returned other shapes: explainers +returned lists of plotly JSON strings, explorers returned a single +``{"data", "type", "config"}`` dict. :func:`normalize_artifacts` upgrades +both legacy shapes, so old pickled explanations, old saved explorations and +legacy plugin components keep working. +""" + +import base64 +from typing import Annotated, Any, Dict, List, Literal, Optional, Union + +from pydantic import ( + BaseModel, + Field, + TypeAdapter, + ValidationError, + field_validator, + model_validator, +) + + +def _detect_mime(data: bytes) -> str: + """Guess the MIME type of raw image bytes from their magic numbers. + + Parameters + ---------- + data : bytes + Raw image bytes. + + Returns + ------- + str + The detected MIME type, or ``"image/png"`` when unknown. + """ + import filetype + + kind = filetype.guess(data) + return kind.mime if kind is not None else "image/png" + + +class Artifact(BaseModel): + """Base class for typed render artifacts. + + Attributes + ---------- + type : str + Discriminator naming the artifact kind; fixed per subclass. + title : Optional[str] + Human readable title shown above the rendered artifact. + role : Literal["input", "explanation"] + Role indicating artifact type, defaulting to explanation. + """ + + type: str + title: Optional[str] = None + role: Literal["input", "explanation"] = "explanation" + + def to_dict(self) -> Dict[str, Any]: + """Serialize the artifact to its wire format. + + Returns + ------- + Dict[str, Any] + ``{"type", "payload", "title"}`` with a JSON-serializable payload. + """ + return self.model_dump() + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "Artifact": + """Deserialize a wire format dict into the matching artifact subclass. + + Parameters + ---------- + data : Dict[str, Any] + A dict with ``type``, ``payload`` and optionally ``title``. + + Returns + ------- + Artifact + An instance of the subclass matching ``data["type"]``. + + Raises + ------ + ValueError + If the type is unknown or the payload is malformed. + """ + try: + return _ANY_ARTIFACT_ADAPTER.validate_python(data) + except ValidationError as error: + raise ValueError(f"Invalid artifact dict: {error}") from error + + +class PlotlyArtifact(Artifact): + """Artifact holding a plotly figure serialized as JSON. + + Attributes + ---------- + payload : str + JSON string produced by ``plotly.io.to_json``. A live plotly + ``Figure`` may be passed instead; it is serialized on validation. + """ + + type: Literal["plotly"] = "plotly" + payload: str + + @field_validator("payload", mode="before") + @classmethod + def _serialize_figure(cls, value: Any) -> Any: + if not isinstance(value, str) and hasattr(value, "to_plotly_json"): + import plotly.io as pio + + return pio.to_json(value) + return value + + +class TableCell(BaseModel): + """Reference to a single table cell, 0-indexed relative to ``rows``. + + Attributes + ---------- + row : int + Row index of the cell. + column : int + Column index of the cell. + """ + + row: int = Field(ge=0) + column: int = Field(ge=0) + + +class TablePayload(BaseModel): + """Payload of a table artifact. + + Attributes + ---------- + columns : List[str] + Column headers. + rows : List[List[Any]] + Table rows; every row must have ``len(columns)`` cells. + highlight : List[TableCell] + Cells to emphasise when rendered. + """ + + columns: List[str] + rows: List[List[Any]] + highlight: List[TableCell] = Field(default_factory=list) + + @model_validator(mode="after") + def _check_shape(self) -> "TablePayload": + for i, row in enumerate(self.rows): + if len(row) != len(self.columns): + raise ValueError( + f"Row {i} has {len(row)} cells, expected {len(self.columns)}." + ) + for cell in self.highlight: + if cell.row >= len(self.rows) or cell.column >= len(self.columns): + raise ValueError( + f"Highlight cell ({cell.row}, {cell.column}) is out of bounds." + ) + return self + + +class TableArtifact(Artifact): + """Artifact holding tabular data with optional highlighted cells. + + Attributes + ---------- + payload : TablePayload + Columns, rows and highlighted cells. + """ + + type: Literal["table"] = "table" + payload: TablePayload + + +class TextArtifact(Artifact): + """Artifact holding plain text rendered preformatted. + + Attributes + ---------- + payload : str + Text content; newlines are preserved when rendered. + """ + + type: Literal["text"] = "text" + payload: str + + +class ImagePayload(BaseModel): + """Payload of an image artifact. + + Attributes + ---------- + data : str + Base64-encoded image bytes. Raw ``bytes`` may be passed instead; + they are encoded on validation. + mime : str + MIME type of the encoded image. + """ + + data: str + mime: str = "image/png" + + @field_validator("data", mode="before") + @classmethod + def _encode_bytes(cls, value: Any) -> Any: + if isinstance(value, (bytes, bytearray)): + return base64.b64encode(bytes(value)).decode("ascii") + if isinstance(value, str): + try: + base64.b64decode(value, validate=True) + except Exception as error: + raise ValueError("data is not valid base64.") from error + return value + + +class ImageArtifact(Artifact): + """Artifact holding a base64-encoded image. + + Attributes + ---------- + payload : ImagePayload + Base64 data and MIME type. + """ + + type: Literal["image"] = "image" + payload: ImagePayload + + @classmethod + def from_dashai_image( + cls, image: Any, title: Optional[str] = None + ) -> "ImageArtifact": + """Build an image artifact from a dataset :class:`DashAIImage` value. + + Parameters + ---------- + image : DashAIImage + A dataset image instance carrying raw bytes. + title : Optional[str] + Human readable title shown above the artifact. + + Returns + ------- + ImageArtifact + The artifact wrapping the image bytes. + + Raises + ------ + ValueError + If the image has no bytes available. + """ + if getattr(image, "bytes", None) is None: + raise ValueError("DashAIImage has no bytes available.") + return cls( + payload=ImagePayload(data=image.bytes, mime=_detect_mime(image.bytes)), + title=title, + ) + + +AnyArtifact = Annotated[ + Union[PlotlyArtifact, TableArtifact, TextArtifact, ImageArtifact], + Field(discriminator="type"), +] + +_ANY_ARTIFACT_ADAPTER: TypeAdapter = TypeAdapter(AnyArtifact) + + +class ArtifactGroup(BaseModel): + """One selectable entry inside a :class:`GroupedArtifacts`. + + A group is a titled batch of leaf artifacts (e.g. a summary table next to + its plot) shown together when its entry is selected. It cannot itself + contain another group: ``artifacts`` is typed as leaf :data:`AnyArtifact` + only. + + Attributes + ---------- + title : Optional[str] + Human readable label for this entry, shown as one row in the parent + group's selector. + artifacts : List[AnyArtifact] + The leaf artifacts shown when this entry is selected, in display + order. + """ + + title: Optional[str] = None + artifacts: List[AnyArtifact] + + +class GroupedArtifacts(BaseModel): + """A selector over several :class:`ArtifactGroup` entries. + + Lets a component (typically a global explainer producing one set of + artifacts per curve/count) return a single interactive unit: the frontend + renders a selector listing every group's title and shows one group's + artifacts at a time. A component may return several ``GroupedArtifacts`` + in its ``plot`` output; each becomes its own independent selector. + + Attributes + ---------- + title : Optional[str] + Optional overall title for the selector. + groups : List[ArtifactGroup] + The selectable groups, in listing order. + """ + + type: Literal["grouped"] = "grouped" + title: Optional[str] = None + groups: List[ArtifactGroup] + + def to_dict(self) -> Dict[str, Any]: + """Serialize the grouped artifacts to their wire format. + + Returns + ------- + Dict[str, Any] + ``{"type": "grouped", "title", "groups"}`` with each group's + artifacts serialized to their own wire format dicts. + """ + return self.model_dump() + + +AnyArtifactOrGroup = Annotated[ + Union[PlotlyArtifact, TableArtifact, TextArtifact, ImageArtifact, GroupedArtifacts], + Field(discriminator="type"), +] + + +def _legacy_explorer_artifact(item: Dict[str, Any]) -> Dict[str, Any]: + """Convert a legacy explorer result dict into an artifact dict. + + Parameters + ---------- + item : Dict[str, Any] + A dict with the old explorer contract ``{"data", "type", "config"}``. + + Returns + ------- + Dict[str, Any] + The equivalent artifact dict; unknown legacy types degrade to a + text artifact. + """ + legacy_type = item.get("type") + data = item.get("data") + if legacy_type == "plotly_json" and isinstance(data, str): + return PlotlyArtifact(payload=data).to_dict() + if legacy_type == "tabular" and isinstance(data, dict): + columns = ["index", *data.keys()] + index_keys: List[Any] = [] + for column_values in data.values(): + if isinstance(column_values, dict): + for key in column_values: + if key not in index_keys: + index_keys.append(key) + rows = [ + [key, *(data[column].get(key) for column in data)] for key in index_keys + ] + return TableArtifact(payload=TablePayload(columns=columns, rows=rows)).to_dict() + if legacy_type == "image_base64" and isinstance(data, str): + return ImageArtifact(payload=ImagePayload(data=data)).to_dict() + return TextArtifact(payload=str(data)).to_dict() + + +def normalize_artifacts( + items: Any, *, create_grouped: bool = False +) -> List[Dict[str, Any]]: + """Coerce any component output into a list of artifact/group wire dicts. + + Handles current values (``Artifact`` or :class:`GroupedArtifacts` + instances, or their wire dicts) and legacy shapes: plain plotly JSON + strings from old explainers, and ``{"data", "type", "config"}`` dicts + from old explorers. Anything else is stringified into a text artifact so + the frontend never receives an unrenderable value. + + Every leaf artifact, whether at the top level or nested inside a + group's ``artifacts``, is stamped with a flat, sequential ``index`` so + the frontend (and the plot override endpoints) can address any leaf by a + single integer regardless of nesting depth. + + Parameters + ---------- + items : Any + The value returned by an explainer ``plot`` method, an explorer + ``get_results`` method, or loaded from a persisted result file. + create_grouped : bool, optional + When ``True``, wrap a list of leaf artifacts into a single grouped + artifact container with one selectable group per input item. This is + useful for local explainers whose output is a list of per-instance + artifacts. + + Returns + ------- + List[Dict[str, Any]] + A list of artifact/grouped wire dicts; leaf artifacts carry an + ``"index"`` key. A grouped dict is + ``{"type": "grouped", "title", "groups": [{"title", "artifacts"}]}``. + """ + if items is None: + return [] + if isinstance(items, (str, dict, Artifact, GroupedArtifacts)): + items = [items] + + next_index = 0 + + def normalize_leaf(item: Any) -> Dict[str, Any]: + nonlocal next_index + if isinstance(item, Artifact): + data = item.to_dict() + elif isinstance(item, str): + data = PlotlyArtifact(payload=item).to_dict() + elif isinstance(item, dict) and "type" in item and "payload" in item: + data = {"title": None, "role": "explanation", **item} + elif isinstance(item, dict) and "type" in item and "data" in item: + data = _legacy_explorer_artifact(item) + else: + data = TextArtifact(payload=str(item)).to_dict() + data["index"] = next_index + next_index += 1 + return data + + def normalize_group(group: Any) -> Dict[str, Any]: + if isinstance(group, ArtifactGroup): + title, artifacts = group.title, group.artifacts + else: + title, artifacts = group.get("title"), group.get("artifacts", []) + return { + "title": title, + "artifacts": [normalize_leaf(a) for a in artifacts], + } + + def normalize_item(item: Any) -> Dict[str, Any]: + if isinstance(item, GroupedArtifacts): + return { + "type": "grouped", + "title": item.title, + "groups": [normalize_group(g) for g in item.groups], + } + if isinstance(item, dict) and item.get("type") == "grouped": + return { + "type": "grouped", + "title": item.get("title"), + "groups": [normalize_group(g) for g in item.get("groups", [])], + } + return normalize_leaf(item) + + def is_grouped(value: Any) -> bool: + return isinstance(value, GroupedArtifacts) or ( + isinstance(value, dict) and value.get("type") == "grouped" + ) + + # Migrate a flat list of per instance leaf artifacts (e.g. an old local + # explainer's output) into a single grouped artifact with one selectable + # group per instance. A payload that is already grouped is passed through + # unchanged. + if create_grouped and items and not is_grouped(items[0]): + groups = [ + {"title": leaf.get("title"), "artifacts": [leaf]} + for leaf in (normalize_leaf(item) for item in items) + ] + return [{"type": "grouped", "title": None, "groups": groups}] + + return [normalize_item(item) for item in items] + + +def build_tabular_input_artifact( + feature_names: List[str], + instance_values: List[Any], + title: Optional[str] = None, +) -> "TableArtifact": + """Build an input artifact holding one instance's feature values. + + Parameters + ---------- + feature_names : List[str] + Column headers, one per feature. + instance_values : List[Any] + The feature values fed to the model for this instance. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + TableArtifact + A single-row table artifact with role "input". + """ + return TableArtifact( + payload=TablePayload( + columns=[str(name) for name in feature_names], + rows=[list(instance_values)], + ), + title=title, + role="input", + ) + + +def build_text_input_artifact(text: str, title: Optional[str] = None) -> "TextArtifact": + """Build an input artifact holding the text fed to the model. + + Parameters + ---------- + text : str + The input text for this instance. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + TextArtifact + A text artifact with role "input". + """ + return TextArtifact(payload=text, title=title, role="input") + + +def build_image_input_artifact( + image: Any, title: Optional[str] = None +) -> "ImageArtifact": + """Build an input artifact from the image fed to the model. + + Parameters + ---------- + image : DashAIImage + The input image instance for this explained sample. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + ImageArtifact + An image artifact with role "input". + """ + artifact = ImageArtifact.from_dashai_image(image, title=title) + artifact.role = "input" + return artifact diff --git a/DashAI/back/dataloaders/classes/dashai_dataset.py b/DashAI/back/dataloaders/classes/dashai_dataset.py index c3fbc44b7..e8f3ed855 100644 --- a/DashAI/back/dataloaders/classes/dashai_dataset.py +++ b/DashAI/back/dataloaders/classes/dashai_dataset.py @@ -264,7 +264,9 @@ def _compute_numeric_metadata(self, dataset_df) -> dict: dict Dictionary with statistics for each numeric column. """ - numeric_keys = self._get_numeric_columns() + numeric_keys = [ + k for k in self._get_numeric_columns() if k in dataset_df.columns + ] numeric_cols = dataset_df[numeric_keys] numeric_stats = {} @@ -316,7 +318,9 @@ def _compute_categorical_metadata(self, dataset_df) -> dict: dict Dictionary with statistics for each categorical column. """ - categorical_keys = self._get_categorical_columns() + categorical_keys = [ + k for k in self._get_categorical_columns() if k in dataset_df.columns + ] categorical_cols = dataset_df[categorical_keys] categorical_stats = {} @@ -388,6 +392,9 @@ def _compute_quality_metadata(self, dataset_df) -> dict: Dictionary with quality indicators including completeness, constant columns, high cardinality columns, and quality score. """ + if dataset_df.empty: + return {} + # Count rows with missing values rows_with_any_nan = int(dataset_df.isna().any(axis=1).sum()) rows_with_multiple_nan = int((dataset_df.isna().sum(axis=1) > 1).sum()) @@ -465,7 +472,9 @@ def _compute_correlations(self, dataset_df) -> dict: dict Nested dictionary representing the correlation matrix. """ - numeric_keys = self._get_numeric_columns() + numeric_keys = [ + k for k in self._get_numeric_columns() if k in dataset_df.columns + ] numeric_cols = dataset_df[numeric_keys] if numeric_cols.empty: @@ -509,6 +518,11 @@ def remove_columns(self, column_names: Union[str, List[str]]) -> "DashAIDataset" # Update self with modified dataset attributes self.__dict__.update(modified_dataset.__dict__) + # Keep self.types in sync so Arrow metadata stays consistent + if self.types is not None: + for col in column_names: + self.types.pop(col, None) + return self @beartype diff --git a/DashAI/back/dataset_sources/openml_dataset_source.py b/DashAI/back/dataset_sources/openml_dataset_source.py index 202e398c6..be8dc2a94 100644 --- a/DashAI/back/dataset_sources/openml_dataset_source.py +++ b/DashAI/back/dataset_sources/openml_dataset_source.py @@ -197,7 +197,7 @@ def _meta(did: str) -> tuple[str, tuple[str, ...]] | None: metas = list(pool.map(_meta, ids)) entries = [] - for row, did, meta in zip(rows, ids, metas): + for row, did, meta in zip(rows, ids, metas, strict=False): description = "" dataset_tags: list[str] = [] if meta is not None: diff --git a/DashAI/back/dependencies/config_builder.py b/DashAI/back/dependencies/config_builder.py index 3323340b9..2966bc203 100644 --- a/DashAI/back/dependencies/config_builder.py +++ b/DashAI/back/dependencies/config_builder.py @@ -59,6 +59,7 @@ def build_config_dict( config["RUNS_PATH"] = local_path / config["RUNS_PATH"] config["IMAGES_PATH"] = local_path / config["IMAGES_PATH"] config["DATAFILE_PATH"] = local_path / config["DATAFILE_PATH"] + config["COMPONENT_PATH"] = local_path / config["COMPONENT_PATH"] config["FRONT_BUILD_PATH"] = pathlib.Path(config["FRONT_BUILD_PATH"]).absolute() config["BACK_PATH"] = pathlib.Path(config["BACK_PATH"]).absolute() config["LOGGING_LEVEL"] = getattr(logging, logging_level) diff --git a/DashAI/back/dependencies/database/models.py b/DashAI/back/dependencies/database/models.py index cdf57d968..c1160db89 100644 --- a/DashAI/back/dependencies/database/models.py +++ b/DashAI/back/dependencies/database/models.py @@ -342,12 +342,13 @@ class GlobalExplainer(Base): Table to store all the information about a global explainer. """ id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String, unique=True, nullable=False) + name: Mapped[str] = mapped_column(String, unique=True, nullable=True) run_id: Mapped[int] = mapped_column(nullable=False) huey_id: Mapped[str] = mapped_column(String, nullable=True) explainer_name: Mapped[str] = mapped_column(String, nullable=False) explanation_path: Mapped[str] = mapped_column(String, nullable=True) plot_path: Mapped[str] = mapped_column(String, nullable=True) + plot_overrides: Mapped[JSON] = mapped_column(JSON, nullable=True) parameters: Mapped[JSON] = mapped_column(JSON) created: Mapped[DateTime] = mapped_column(DateTime, default=datetime.now) status: Mapped[Enum] = mapped_column( @@ -383,13 +384,15 @@ class LocalExplainer(Base): Table to store all the information about a local explainer. """ id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String, unique=True, nullable=False) + name: Mapped[str] = mapped_column(String, unique=True, nullable=True) run_id: Mapped[int] = mapped_column(nullable=False) huey_id: Mapped[str] = mapped_column(String, nullable=True) explainer_name: Mapped[str] = mapped_column(String, nullable=False) dataset_id: Mapped[int] = mapped_column(nullable=False) explanation_path: Mapped[str] = mapped_column(String, nullable=True) plots_path: Mapped[str] = mapped_column(String, nullable=True) + plot_overrides: Mapped[JSON] = mapped_column(JSON, nullable=True) + input_dataset_path: Mapped[str] = mapped_column(String, nullable=True) parameters: Mapped[JSON] = mapped_column(JSON) fit_parameters: Mapped[JSON] = mapped_column(JSON) scope: Mapped[JSON] = mapped_column(JSON) @@ -658,6 +661,10 @@ class GenerativeSessionParameterHistory(Base): nullable=False, ) parameters: Mapped[JSON] = mapped_column(JSON, nullable=False) + # Model active when this snapshot was taken. Nullable so pre-migration rows + # remain valid; the parameters-history derivation only emits a model-change + # event when two consecutive snapshots both carry a model name that differs. + model_name: Mapped[str] = mapped_column(String, nullable=True) modified_at: Mapped[DateTime] = mapped_column( DateTime, default=datetime.now, diff --git a/DashAI/back/dependencies/downloads/__init__.py b/DashAI/back/dependencies/downloads/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py new file mode 100644 index 000000000..555ede709 --- /dev/null +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -0,0 +1,383 @@ +"""Source-agnostic mixin for components that download external artifacts. + +Each downloadable component owns a directory ``/`` and +is responsible for downloading its artifacts into that directory and removing +them from it. There is no shared cache; ``HFDownloadableMixin`` covers the common +HuggingFace case by downloading each repo into the component's own folder. +""" + +import logging +import os +import pathlib +import shutil +from typing import Callable, List, Optional, Tuple, Union + +from huggingface_hub import snapshot_download +from kink import di + +logger = logging.getLogger(__name__) + +# report(fraction, message): fraction in [0, 1] or None for indeterminate. +ProgressReporter = Callable[[Optional[float], Optional[str]], None] + + +def _components_root() -> pathlib.Path: + """Return the base directory that holds one folder per component.""" + return pathlib.Path(di["config"]["COMPONENT_PATH"]) + + +class DownloadableMixin: + """Marks a component as requiring an explicit download. + + Subclasses implement ``is_downloaded`` and ``download`` for their own source + and store artifacts under ``component_dir()``. ``delete`` defaults to removing + that directory. See ``HFDownloadableMixin`` for the HuggingFace case. + """ + + REQUIRES_DOWNLOAD: bool = True + DOWNLOAD_SIZE_BYTES: Optional[int] = None + + @classmethod + def component_dir(cls) -> pathlib.Path: + """Return this component's own storage directory. + + Returns + ------- + pathlib.Path + ``/``. + """ + return _components_root() / cls.__name__ + + @classmethod + def is_downloaded(cls) -> bool: + """Return whether the component's artifacts are present locally.""" + raise NotImplementedError + + @classmethod + def download(cls, report: Optional[ProgressReporter] = None) -> None: + """Fetch the component's artifacts into ``component_dir()``.""" + raise NotImplementedError + + @classmethod + def delete(cls) -> None: + """Remove the component's downloaded artifacts.""" + shutil.rmtree(cls.component_dir(), ignore_errors=True) + + +class HFDownloadableMixin(DownloadableMixin): + """Downloadable component whose artifacts are HuggingFace repos. + + Each repo is downloaded into ``component_dir()/``. Subclasses set + ``HF_REPOS`` or, for a dynamic repo (e.g. derived from a per-subclass + ``MODEL_NAME``), override ``hf_repos``. + + ``HF_REPOS`` entries accept two shapes: + + * ``(repo_id, repo_type)`` -- full snapshot download (original behavior). + * ``(repo_id, repo_type, allow_patterns)`` -- partial download; only files + matching the glob patterns in ``allow_patterns`` are fetched. + + ``HF_IGNORE_PATTERNS`` is applied to every repo download to skip the + non-PyTorch weight formats HuggingFace repos ship alongside the PyTorch / + safetensors weights (TensorFlow, Flax, Rust, ONNX, OpenVINO, CoreML). These + are never used by ``from_pretrained`` here, so dropping them shrinks the + download without affecting fine-tuning or inference. It keeps both ``.bin`` + and ``.safetensors`` so any model still has a loadable weight. + """ + + HF_REPOS: List[Union[Tuple[str, str], Tuple[str, str, List[str]]]] = [] + #: Alternate-framework artifacts to skip on every download (``*`` matches + #: path separators in ``huggingface_hub`` glob semantics, so these match at + #: any depth, e.g. ``unet/diffusion_flax_model.msgpack``). + HF_IGNORE_PATTERNS: Optional[List[str]] = [ + "*.h5", + "*.msgpack", + "*.ot", + "*.onnx", + "*.onnx_data", + "*.tflite", + "*.mlmodel", + "*openvino*", + "*coreml*", + ] + + @classmethod + def hf_repos(cls) -> List[Union[Tuple[str, str], Tuple[str, str, List[str]]]]: + """Return the repo entries this component needs. + + Returns + ------- + list of tuple + Each entry is either ``(repo_id, repo_type)`` or + ``(repo_id, repo_type, allow_patterns)``. + """ + return list(cls.HF_REPOS) + + @classmethod + def _unpack_entry( + cls, + entry: Union[Tuple[str, str], Tuple[str, str, List[str]]], + ) -> Tuple[str, str, Optional[List[str]]]: + """Normalise a repo entry into ``(repo_id, repo_type, allow_patterns)``. + Parameters + ---------- + entry : tuple + Either a 2-tuple ``(repo_id, repo_type)`` or a 3-tuple + ``(repo_id, repo_type, allow_patterns)``. + + Returns + ------- + tuple of (str, str, list[str] or None) + ``repo_id``, ``repo_type``, and ``allow_patterns`` (``None`` when + the entry was a 2-tuple, meaning a full snapshot download). + + Raises + ------ + ValueError + If ``entry`` has a length other than 2 or 3. + """ + if len(entry) == 2: + rid, rtype = entry + return rid, rtype, None + if len(entry) == 3: + rid, rtype, patterns = entry + return rid, rtype, patterns + raise ValueError( + f"HF_REPOS entries must be 2- or 3-tuples; " + f"got length {len(entry)}: {entry!r}" + ) + + @classmethod + def _repo_dir(cls, repo_id: str) -> pathlib.Path: + """Return the local directory for a single repo under component_dir(). + + Parameters + ---------- + repo_id : str + HuggingFace repo identifier, e.g. ``"owner/model-name"``. + + Returns + ------- + pathlib.Path + ``component_dir()/``. + """ + return cls.component_dir() / repo_id.split("/")[-1] + + @classmethod + def is_downloaded(cls) -> bool: + """Return whether all repo directories exist and are non-empty. + + Returns + ------- + bool + ``True`` when every repo listed in ``hf_repos()`` has a non-empty + local directory; ``False`` otherwise (including when the list is + empty). + """ + repos = cls.hf_repos() + return bool(repos) and all( + cls._repo_dir(rid).is_dir() and any(cls._repo_dir(rid).iterdir()) + for rid, *_ in repos + ) + + @classmethod + def _local_or_repo(cls, repo_id: str) -> str: + """Return the local dir for a repo if downloaded, else the repo id. + + Lets multi-repo components (e.g. ControlNet pipelines) load each repo + from the component's own download folder when present, falling back to + the Hub otherwise. Downloading is enforced by the run/session gates. + + Parameters + ---------- + repo_id : str + HuggingFace repo identifier. + + Returns + ------- + str + A local path (when the repo is present) or ``repo_id``. + """ + try: + target = cls._repo_dir(repo_id) + if target.is_dir() and any(target.iterdir()): + return str(target) + except Exception: + pass + return repo_id + + @classmethod + def download(cls, report: Optional[ProgressReporter] = None) -> None: + """Download all repos listed in ``hf_repos()`` into ``component_dir()``. + + Parameters + ---------- + report : ProgressReporter, optional + Callback invoked before each repo download with + ``report(None, "Downloading ")``. ``None`` means no + progress reporting. + """ + # Force the classic HTTP/LFS transfer path. The Xet backend can return + # a 404 on its read-token endpoint for some repos (e.g. bert-base- + # uncased), which aborts the whole download; the classic path is + # slower but reliable. + os.environ["HF_HUB_DISABLE_XET"] = "1" + try: + from huggingface_hub import constants as hf_constants + + hf_constants.HF_HUB_DISABLE_XET = True + except Exception: + pass + + for entry in cls.hf_repos(): + rid, rtype, allow_patterns = cls._unpack_entry(entry) + target = cls._repo_dir(rid) + target.mkdir(parents=True, exist_ok=True) + if report is not None: + # snapshot_download exposes no aggregate byte count, so progress + # is reported as indeterminate (None) with a phase message. + report(None, f"Downloading {rid}") + kwargs = {} + if allow_patterns is not None: + kwargs["allow_patterns"] = allow_patterns + if cls.HF_IGNORE_PATTERNS: + kwargs["ignore_patterns"] = list(cls.HF_IGNORE_PATTERNS) + snapshot_download( + repo_id=rid, repo_type=rtype, local_dir=str(target), **kwargs + ) + + +class HFPretrainedDownloadMixin(HFDownloadableMixin): + """HuggingFace mixin for models built around a single ``MODEL_NAME`` repo. + + Covers the common ``from_pretrained`` case: the repo is derived from the + subclass ``MODEL_NAME`` and ``_pretrained_source`` returns where to load + from (a saved run, the local download folder, or the Hub as a fallback). + """ + + MODEL_NAME: str = "" + + @classmethod + def hf_repos(cls): + """Derive the single repo entry from ``MODEL_NAME``. + + Returns + ------- + list of tuple of (str, str) + ``[(MODEL_NAME, "model")]`` or an empty list when unset. + """ + return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] + + def _pretrained_source(self, pretrained_dir: Optional[str] = None) -> str: + """Resolve where ``from_pretrained`` should load from. + + Prefers an explicit ``pretrained_dir`` (a saved run), then the local + component download folder when present, and finally falls back to the + Hub repo id. Downloading is enforced by the run/session gates before + real use; the Hub fallback keeps direct instantiation working when + nothing has been downloaded. + + Parameters + ---------- + pretrained_dir : str or None + Directory of a previously saved run, if any. + + Returns + ------- + str + A path or repo id accepted by ``from_pretrained``. + """ + if pretrained_dir: + return pretrained_dir + try: + if self.is_downloaded(): + return str(self._repo_dir(self.MODEL_NAME)) + except Exception: + pass + return self.MODEL_NAME + + +class TorchvisionDownloadMixin(DownloadableMixin): + """Downloadable mixin for torchvision models with ImageNet-pretrained weights. + + torchvision fetches pretrained weights into a global ``torch.hub`` cache. + This mixin redirects that cache to ``component_dir()`` so the weights are + stored and gated like any other downloadable component. Subclasses build + their backbone inside :meth:`local_hub` so the pretrained weights are read + from (and written to) the component's own folder. + + .. note:: + The download provides the ImageNet weights used when the model is built + with ``pretrained=True`` (the default). Training a fresh model with + ``pretrained=False`` needs no weights but is still gated as a + download-required component. + """ + + @classmethod + def _weights(cls): + """Return the torchvision weights enum member to download. + + Returns + ------- + torchvision.models.WeightsEnum + The pretrained weights descriptor whose file is fetched. + """ + raise NotImplementedError + + @classmethod + def _checkpoints_dir(cls): + """Return the directory where torch.hub stores downloaded checkpoints.""" + return cls.component_dir() / "checkpoints" + + @classmethod + def local_hub(cls): + """Context manager that points ``torch.hub`` at ``component_dir()``. + + Returns + ------- + contextlib.AbstractContextManager + A context that temporarily sets the torch hub directory to this + component's folder and restores the previous value on exit. + """ + import contextlib + + import torch + + @contextlib.contextmanager + def _ctx(): + old = torch.hub.get_dir() + cls.component_dir().mkdir(parents=True, exist_ok=True) + torch.hub.set_dir(str(cls.component_dir())) + try: + yield + finally: + torch.hub.set_dir(old) + + return _ctx() + + @classmethod + def is_downloaded(cls) -> bool: + """Return whether the pretrained weights file is present locally. + + Returns + ------- + bool + ``True`` when the component's ``checkpoints`` folder exists and is + non-empty. + """ + ckpt = cls._checkpoints_dir() + return ckpt.is_dir() and any(ckpt.iterdir()) + + @classmethod + def download(cls, report: Optional[ProgressReporter] = None) -> None: + """Fetch the pretrained weights into ``component_dir()``. + + Parameters + ---------- + report : ProgressReporter, optional + Callback invoked with an indeterminate progress message. + """ + if report is not None: + report(None, f"Downloading {cls.__name__} weights") + with cls.local_hub(): + cls._weights().get_state_dict(progress=False) diff --git a/DashAI/back/dependencies/downloads/nested.py b/DashAI/back/dependencies/downloads/nested.py new file mode 100644 index 000000000..45b65c973 --- /dev/null +++ b/DashAI/back/dependencies/downloads/nested.py @@ -0,0 +1,143 @@ +"""Discover nested downloadable components inside a parameters dict. + +A DashAI component may take another component as a parameter (a +``component_field``). That child may itself require a download, and the child +may in turn nest further components. This module walks a parameters dict the +same way :class:`~DashAI.back.models.model_factory.ModelFactory` does when it +instantiates the model graph, so the set of components it reports matches the +set that would actually be built. + +Two value shapes are handled, mirroring ``ModelFactory._process_param``: + +* the canonical ``{"component": , "params": {...}}`` descriptor, and +* the frontend-wrapped ``{"properties": {"component": ..., "params": ...}}``. +""" + +from typing import Any, Dict, Iterator, List, Optional, Tuple + + +def _unwrap(value: Any) -> Any: + """Strip the single-key ``properties`` wrapper the frontend adds. + + Parameters + ---------- + value : Any + A parameter value as stored in a parameters dict. + + Returns + ------- + Any + ``value["properties"]`` when ``value`` is a ``{"properties": ...}`` + wrapper, otherwise ``value`` unchanged. + """ + if isinstance(value, dict) and "properties" in value and len(value) == 1: + return value["properties"] + return value + + +def _iter_value( + value: Any, parent: Optional[str] +) -> Iterator[Tuple[str, Optional[str]]]: + """Yield ``(component_name, parent_name)`` for a single parameter value. + + Recurses into the selected component's own parameters so components nested + at any depth are reported. + + Parameters + ---------- + value : Any + A parameter value, possibly a nested component descriptor. + parent : str or None + Name of the component that owns this value, used as the ``parent`` of + any component found directly inside it. + + Yields + ------ + tuple of (str, str or None) + The nested component name and the name of its enclosing component. + """ + value = _unwrap(value) + if not (isinstance(value, dict) and "component" in value): + return + + parent_component_name = value["component"] + inner = value.get("params", {}).get("comp", {}) + if inner == {}: + name = parent_component_name + params = value.get("params", {}) + else: + name = inner.get("component") + params = inner.get("params", {}) + + if name: + yield name, parent + if isinstance(params, dict): + for sub_value in params.values(): + yield from _iter_value(sub_value, name) + + +def iter_config_components( + parameters: Dict[str, Any], +) -> Iterator[Tuple[str, Optional[str]]]: + """Yield ``(component_name, parent_name)`` for every nested component. + + Parameters + ---------- + parameters : dict + A parameters dict as produced by the DashAI configuration UI. + + Yields + ------ + tuple of (str, str or None) + Each nested component name paired with its enclosing component name + (``None`` at the top level). + """ + for value in parameters.values(): + yield from _iter_value(value, None) + + +def missing_downloads( + parameters: Dict[str, Any], + component_registry, +) -> List[Dict[str, Any]]: + """Return metadata for nested components that still need downloading. + + Each candidate is reconciled against the filesystem via + ``refresh_download_status`` so a component downloaded after startup (in the + worker process) is recognised without an API restart. + + Parameters + ---------- + parameters : dict + A parameters dict as produced by the DashAI configuration UI. + component_registry : ComponentRegistry + The registry used to resolve component classes and download state. + + Returns + ------- + list of dict + One entry per not-yet-downloaded nested component, each with + ``name``, ``parent``, and ``download_size_bytes`` keys. Empty when + every nested download-required component is already present. + """ + missing: List[Dict[str, Any]] = [] + seen = set() + for name, parent in iter_config_components(parameters): + if name in seen or name not in component_registry: + continue + seen.add(name) + component_class = component_registry[name]["class"] + if not getattr(component_class, "REQUIRES_DOWNLOAD", False): + continue + if component_registry.refresh_download_status(name): + continue + missing.append( + { + "name": name, + "parent": parent, + "download_size_bytes": getattr( + component_class, "DOWNLOAD_SIZE_BYTES", None + ), + } + ) + return missing diff --git a/DashAI/back/dependencies/job_queues/base_job_queue.py b/DashAI/back/dependencies/job_queues/base_job_queue.py index 17c579ca4..b965affca 100644 --- a/DashAI/back/dependencies/job_queues/base_job_queue.py +++ b/DashAI/back/dependencies/job_queues/base_job_queue.py @@ -107,6 +107,28 @@ def to_list(self) -> List[BaseJob]: """ raise NotImplementedError + def report_progress( + self, + job_id: str, + progress: Optional[float], + message: Optional[str] = None, + ) -> None: + """Update the progress of a running job. + + The default implementation is a no-op so queues that do not track + progress remain valid. Concrete queues may override it. + + Parameters + ---------- + job_id: str + Identifier of the job to update. + progress: Optional float + Completion percentage in the range 0-100, or None when unknown. + message: Optional str + Short description of the current phase. + """ + return None + class JobQueueError(Exception): """Exception raised when a method of the job queue fails.""" diff --git a/DashAI/back/dependencies/job_queues/huey_job_queue.py b/DashAI/back/dependencies/job_queues/huey_job_queue.py index bfff16821..e4b18b96a 100644 --- a/DashAI/back/dependencies/job_queues/huey_job_queue.py +++ b/DashAI/back/dependencies/job_queues/huey_job_queue.py @@ -78,6 +78,7 @@ def __init__(self, queue_name: str, path_db: str): ) self._enable_wal() self._ensure_task_copy_table() + self._ensure_progress_columns() self._register_signals() @self.huey.task(context=True, priority=0) @@ -190,7 +191,7 @@ def on_start(signal, task): def on_success(signal, task, *args): exec_sql( ( - "UPDATE task_copy SET status = ?, " + "UPDATE task_copy SET status = ?, progress = 100, " f"last_update = {NOW_MICRO} " "WHERE id = ?" ), @@ -229,6 +230,8 @@ def _ensure_task_copy_table(self): 'deleted', 'error' - last_update (DATETIME NOT NULL): defaults to CURRENT_TIMESTAMP (UTC) - error_msg (TEXT): optional error message when a task fails + - progress (REAL): optional completion percentage in the range 0-100 + - progress_message (TEXT): optional short description of the current phase """ with sqlite3.connect(self.db_path) as conn: conn.execute( @@ -240,7 +243,9 @@ def _ensure_task_copy_table(self): enqueued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, status TEXT NOT NULL, last_update DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - error_msg TEXT + error_msg TEXT, + progress REAL, + progress_message TEXT ) """ ) @@ -251,12 +256,29 @@ def _ensure_task_copy_table(self): ) ) + def _ensure_progress_columns(self): + """Add the progress columns to an existing 'task_copy' table. + + Installs created before progress tracking existed have a 'task_copy' + table without the 'progress' and 'progress_message' columns. SQLite has + no 'ADD COLUMN IF NOT EXISTS', so inspect the current columns via + PRAGMA table_info and add only the ones that are missing. + """ + with sqlite3.connect(self.db_path) as conn: + cur = conn.execute("PRAGMA table_info(task_copy)") + existing = {row[1] for row in cur.fetchall()} + if "progress" not in existing: + conn.execute("ALTER TABLE task_copy ADD COLUMN progress REAL") + if "progress_message" not in existing: + conn.execute("ALTER TABLE task_copy ADD COLUMN progress_message TEXT") + def status(self, job_id: str) -> dict: conn = sqlite3.connect(self.db_path) cur = conn.cursor() cur.execute( """ - SELECT status, last_update, error_msg, job_name + SELECT status, last_update, error_msg, job_name, progress, + progress_message FROM task_copy WHERE id = ? """, (str(job_id),), @@ -270,8 +292,40 @@ def status(self, job_id: str) -> dict: "updated": row[1], "error": row[2], "job_name": row[3], + "progress": row[4], + "progress_message": row[5], } + def report_progress( + self, job_id: str, progress: float | None, message: str | None = None + ) -> None: + """Update the progress of a running job. + + Parameters + ---------- + job_id : str + The UUID of the job (its Huey task id). + progress : float or None + Completion percentage in the range 0-100, or None for jobs whose + total work is unknown (the frontend renders an indeterminate bar). + message : str or None + Optional short description of the current phase. + + Notes + ----- + This also refreshes 'last_update' so the change surfaces through + 'changes_since' and the frontend polling channel. + """ + with sqlite3.connect(self.db_path) as conn: + conn.execute( + ( + "UPDATE task_copy SET progress = ?, progress_message = ?, " + "last_update = STRFTIME('%Y-%m-%d %H:%M:%f','now') " + "WHERE id = ?" + ), + (progress, message, str(job_id)), + ) + def put(self, job: BaseJob) -> int: result = self._execute(job) @@ -284,7 +338,7 @@ def to_list(self) -> list[dict]: cur.execute( """ SELECT id, task_type, job_name, enqueued_at, status, last_update, - error_msg + error_msg, progress, progress_message FROM task_copy ORDER BY last_update DESC """ @@ -304,7 +358,7 @@ def changes_since(self, since: str) -> list[dict]: cur.execute( """ SELECT id, task_type, job_name, enqueued_at, status, last_update, - error_msg + error_msg, progress, progress_message FROM task_copy WHERE last_update >= ? ORDER BY last_update DESC diff --git a/DashAI/back/dependencies/registry/component_registry.py b/DashAI/back/dependencies/registry/component_registry.py index d99145aaf..e97115ab6 100644 --- a/DashAI/back/dependencies/registry/component_registry.py +++ b/DashAI/back/dependencies/registry/component_registry.py @@ -30,6 +30,7 @@ class ComponentRegistry: "description": "...", # An object description. "display_name": "...", # A readable label. "color": "...", # A color associated to the component. + "downloaded": True, # False until a download-required component is fetched. } ``` @@ -64,6 +65,9 @@ def __init__( for component in initial_components: self.register_component(component) + # Ensure every component carries the downloaded flag. + self.seed_download_status() + @property @beartype def registry(self) -> Dict[str, Dict[str, Any]]: @@ -167,6 +171,33 @@ def _get_base_type(self, new_component: type) -> str: return base_classes_cantidates[0].TYPE + @staticmethod + @beartype + def _collect_compatible_components(component: type) -> List[str]: + """Collect the union of ``COMPATIBLE_COMPONENTS`` declared along the MRO. + + Each class in the component's MRO contributes only the entries it + declares itself, so mixins and base classes compose instead of the + first declaration shadowing the rest (e.g. a task mixin declaring the + task plus a model base class declaring its supported explainers). + + Parameters + ---------- + component : type + The component class to inspect. + + Returns + ------- + List[str] + Deduplicated compatible component names in MRO order. + """ + compatible_components: List[str] = [] + for klass in component.__mro__: + for entry in vars(klass).get("COMPATIBLE_COMPONENTS", []): + if entry not in compatible_components: + compatible_components.append(entry) + return compatible_components + @beartype def register_component(self, new_component: Type) -> None: """Register a component within the registry. @@ -225,13 +256,73 @@ def register_component(self, new_component: Type) -> None: else: self._registry[base_type][new_component.__name__] = new_register_component + self._set_download_status(new_register_component) + if hasattr(new_component, "COMPATIBLE_COMPONENTS"): - for compatible_component in new_component.COMPATIBLE_COMPONENTS: + for compatible_component in self._collect_compatible_components( + new_component + ): self._relationship_manager.add_relationship( new_component.__name__, compatible_component, ) + def seed_download_status(self) -> None: + """Populate the ``downloaded`` flag for every registered component. + + Downloadable components are checked via their ``is_downloaded`` method; + all other components are always considered available. + """ + for type_dict in self._registry.values(): + for _name, component_dict in type_dict.items(): + self._set_download_status(component_dict) + + def refresh_download_status(self, name: str) -> bool: + """Recheck a single component's download status and update the registry. + + Parameters + ---------- + name : str + The component class name. + + Returns + ------- + bool + The reconciled ``downloaded`` value. + """ + component_dict = self[name] + return self._set_download_status(component_dict) + + def _set_download_status(self, component_dict: dict) -> bool: + """Set the ``downloaded`` key on a component dict and return the value. + + Parameters + ---------- + component_dict : dict + A registry component dict to update in place. + + Returns + ------- + bool + The resolved download status for the component. + + Notes + ----- + Exceptions from ``is_downloaded()`` are suppressed and treated as + not-downloaded, so a component may appear not-downloaded without raising. + """ + component_class = component_dict["class"] + requires = bool(getattr(component_class, "REQUIRES_DOWNLOAD", False)) + if requires: + try: + downloaded = bool(component_class.is_downloaded()) + except Exception: + downloaded = False + else: + downloaded = True + component_dict["downloaded"] = downloaded + return downloaded + @beartype def unregister_component(self, component: Type) -> None: """Remove a component from the registry. @@ -254,12 +345,11 @@ def unregister_component(self, component: Type) -> None: f"in the registry. Exception: {e}" ) from e - if hasattr(component, "COMPATIBLE_COMPONENTS"): - for compatible_component in component.COMPATIBLE_COMPONENTS: - self._relationship_manager.remove_relationship( - component.__name__, - compatible_component, - ) + for compatible_component in self._collect_compatible_components(component): + self._relationship_manager.remove_relationship( + component.__name__, + compatible_component, + ) @beartype def get_components_by_types( @@ -429,7 +519,8 @@ def get_related_components(self, component_id: str) -> List[Dict[str, Any]]: """Obtain any related component of the given component name. If the component has no related components, then the method returns an empty - list. + list. Related names that are not registered components (e.g. an explainer + declared by a model but provided by an uninstalled plugin) are skipped. Parameters ---------- @@ -454,4 +545,5 @@ def get_related_components(self, component_id: str) -> List[Dict[str, Any]]: return [ self.__getitem__(related_component_id) for related_component_id in self._relationship_manager[component_id] + if self.__contains__(related_component_id) ] diff --git a/DashAI/back/explainability/explainers/contrastive_shap.py b/DashAI/back/explainability/explainers/contrastive_shap.py new file mode 100644 index 000000000..3c6e8243d --- /dev/null +++ b/DashAI/back/explainability/explainers/contrastive_shap.py @@ -0,0 +1,469 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + bool_field, + float_field, + schema_field, + string_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class ContrastiveShapSchema(BaseSchema): + """Schema for ContrastiveShap explainer hyperparameters. + + Configures the foil class the explanation contrasts against and the + background sampling used to fit the underlying SHAP explainer. + """ + + foil_class: schema_field( + string_field(), + placeholder="second_most_probable", + description=MultilingualString( + en=( + "Class to contrast against (the foil in 'why P rather than " + "Q?'). Enter an exact class name, or leave " + "'second_most_probable' to contrast against the runner-up " + "class of each instance." + ), + es=( + "Clase contra la que se contrasta (el foil en '¿por qué P y " + "no Q?'). Ingrese un nombre de clase exacto, o deje " + "'second_most_probable' para contrastar con la segunda clase " + "más probable de cada instancia." + ), + pt=( + "Classe contra a qual contrastar (o foil em 'por que P e não " + "Q?'). Insira um nome de classe exato, ou deixe " + "'second_most_probable' para contrastar com a segunda classe " + "mais provável de cada instância." + ), + zh=( + "对比的目标类别('为什么是P而不是Q'中的Q)。" + "输入准确的类别名称,或保留'second_most_probable'以对比每个实例的第二可能类别。" + ), + de=( + "Klasse, gegen die kontrastiert wird (das Foil in 'warum P " + "statt Q?'). Geben Sie einen exakten Klassennamen ein oder " + "belassen Sie 'second_most_probable', um gegen die " + "zweitwahrscheinlichste Klasse zu kontrastieren." + ), + ), + alias=MultilingualString( + en="Foil class", + es="Clase foil", + pt="Classe foil", + zh="对比类别", + de="Foil-Klasse", + ), + ) # type: ignore + + fit_parameter_sample_background_data: schema_field( + bool_field(), + placeholder=True, + description=MultilingualString( + en=( + "'true' if background data must be sampled; otherwise the " + "entire training set is used. Smaller datasets speed up the " + "algorithm runtime." + ), + es=( + "'true' si se deben muestrear los datos de fondo; de lo " + "contrario se usa el conjunto de entrenamiento completo. " + "Conjuntos más pequeños reducen el tiempo de ejecución." + ), + pt=( + "'true' se os dados de fundo devem ser amostrados; caso " + "contrário, usa-se o conjunto de treinamento completo. " + "Conjuntos menores reduzem o tempo de execução." + ), + zh=( + "如果需要对背景数据进行采样则为'true';否则使用整个训练集。较小的数据集可加速算法运行。" + ), + de=( + "'true', wenn Hintergrunddaten gesamplet werden müssen; sonst " + "wird der gesamte Trainingssatz verwendet. Kleinere " + "Datensätze beschleunigen die Laufzeit." + ), + ), + alias=MultilingualString( + en="Sample background data", + es="Muestrear datos de fondo", + pt="Amostrar dados de fundo", + zh="采样背景数据", + de="Hintergrunddaten samplen", + ), + ) # type: ignore + + fit_parameter_background_fraction: schema_field( + float_field(ge=0, le=1), + placeholder=0.2, + description=MultilingualString( + en=( + "If 'Sample background data' is selected, fraction of " + "background samples to draw from the training set." + ), + es=( + "Si se selecciona 'Muestrear datos de fondo', proporción de " + "muestras de fondo a extraer del conjunto de entrenamiento." + ), + pt=( + "Se 'Amostrar dados de fundo' estiver selecionado, fração de " + "amostras de fundo a extrair do conjunto de treinamento." + ), + zh=("如果选择了'采样背景数据',则为从训练集中抽取的背景样本比例。"), + de=( + "Wenn 'Hintergrunddaten samplen' ausgewählt ist, Anteil der " + "Hintergrundproben aus dem Trainingssatz." + ), + ), + alias=MultilingualString( + en="Background fraction", + es="Fracción de fondo", + pt="Fração de fundo", + zh="背景比例", + de="Hintergrundfraktion", + ), + ) # type: ignore + + +class ContrastiveShap(BaseLocalExplainer): + """Contrastive local explainer: why class P rather than class Q? + + Standard attribution methods answer "why did the model predict P?". + Contrastive explanations answer the question people actually ask: "why P + rather than Q?". This explainer computes Kernel SHAP attributions for both + the predicted class (the fact) and a contrast class (the foil), and + reports the per-feature difference. Features with a large positive delta + are the ones that pushed the model towards the fact and away from the + foil. + + The foil can be a fixed class name, or the second most probable class of + each instance (default). + + References + ---------- + - [1] Miller, T. (2019). "Explanation in Artificial Intelligence: + Insights from the Social Sciences." Artificial Intelligence 267. + https://arxiv.org/abs/1706.07269 + - [2] Lundberg, S.M. & Lee, S.I. (2017). "A Unified Approach to + Interpreting Model Predictions." NeurIPS 30. + https://arxiv.org/abs/1705.07874 + """ + + COMPATIBLE_COMPONENTS = ["TabularClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Contrastive SHAP (why P rather than Q)", + es="SHAP contrastivo (por qué P y no Q)", + pt="SHAP contrastivo (por que P e não Q)", + zh="对比SHAP(为什么是P而不是Q)", + de="Kontrastives SHAP (warum P statt Q)", + ) + DESCRIPTION = MultilingualString( + en=( + "Explains why the model predicted one class rather than another " + "by contrasting SHAP attributions between the two classes." + ), + es=( + "Explica por qué el modelo predijo una clase y no otra, " + "contrastando las atribuciones SHAP entre ambas clases." + ), + pt=( + "Explica por que o modelo previu uma classe e não outra, " + "contrastando as atribuições SHAP entre as duas classes." + ), + zh=("通过对比两个类别之间的SHAP归因,解释模型为什么预测一个类别而不是另一个。"), + de=( + "Erklärt, warum das Modell eine Klasse statt einer anderen " + "vorhergesagt hat, durch Kontrastierung der SHAP-Attributionen " + "beider Klassen." + ), + ) + COLOR = "#00695C" + SCHEMA = ContrastiveShapSchema + + def __init__( + self, + model: BaseModel, + foil_class: str = "second_most_probable", + ) -> None: + """Initialize a new instance of a ContrastiveShap explainer. + + Parameters + ---------- + model : BaseModel + Model to be explained. + foil_class : str + Name of the class to contrast against, or + 'second_most_probable' to use the runner-up class per instance. + """ + super().__init__(model) + self.foil_class = foil_class + + def fit( + self, + background_dataset, + sample_background_data=False, + background_fraction=None, + **kwargs, + ): + """Fit the underlying Kernel SHAP explainer on background data. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits; the train split is used + as SHAP background data. + sample_background_data : bool + True if the background data must be sampled. + background_fraction : float + Fraction of the training samples used as background data when + ``sample_background_data`` is True. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + ContrastiveShap + The fitted explainer instance (``self``). + """ + import shap + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = background_dataset + # SHAP calls the model with perturbed frames, which skip the model + # preparation, so the background must be in the model feature space. + x_train = prepare_model_input(self.model, x["train"]) + y_train = y["train"] + + background_data = x_train.to_pandas() + feature_names = list(x_train.column_names) + + if bool(sample_background_data) and background_fraction: + n_samples = max(1, int(background_fraction * len(background_data))) + background_data = shap.sample(background_data, n_samples) + + self.explainer = shap.KernelExplainer( + model=self.model.predict, + data=background_data, + feature_names=feature_names, + ) + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = { + "feature_names": feature_names, + "target_names": list(target_names), + } + + return self + + def _resolve_foil(self, prediction, fact_class: int) -> int: + """Resolve the foil class index for one instance. + + Parameters + ---------- + prediction : np.ndarray + Per-class probabilities for the instance. + fact_class : int + Index of the predicted (fact) class. + + Returns + ------- + int + Index of the foil class. Falls back to the second most probable + class when the configured name is unknown or equals the fact. + """ + import numpy as np + + target_names = self.metadata["target_names"] + if self.foil_class in target_names: + foil = target_names.index(self.foil_class) + if foil != fact_class: + return foil + + order = np.argsort(prediction)[::-1] + return int(order[1]) if len(order) > 1 else fact_class + + def explain_instance(self, instances): + """Compute contrastive SHAP attributions for the given instances. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the fact and foil classes and + the per-feature attribution difference (fact minus foil). + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + from DashAI.back.explainability.model_input import prepare_model_input + + dataset = to_dashai_dataset(instances) + X = prepare_model_input(self.model, dataset).to_pandas() + + predictions = np.asarray(self.model.predict(dataset)) + + shap_values = self.explainer.shap_values(X=X) + # (n_instances, n_features, n_classes) -> (n_instances, n_classes, + # n_features), same normalization used by the KernelShap explainer. + shap_values = np.array(shap_values).transpose(0, 2, 1) + + explanation = {"metadata": self.metadata} + for i, (instance, prediction, contributions) in enumerate( + zip(X.to_numpy(), predictions, shap_values) # noqa: B905 + ): + fact_class = int(np.argmax(prediction)) + foil_class = self._resolve_foil(prediction, fact_class) + delta = contributions[fact_class] - contributions[foil_class] + + explanation[i] = { + "instance_values": instance.tolist(), + "model_prediction": prediction.tolist(), + "fact_class": fact_class, + "foil_class": foil_class, + "fact_shap_values": np.round(contributions[fact_class], 3).tolist(), + "foil_shap_values": np.round(contributions[foil_class], 3).tolist(), + "delta_values": np.round(delta, 3).tolist(), + } + + return explanation + + def _create_plot(self, data, fact_name, foil_name, fact_prob, foil_prob): + """Create the contrastive bar plot for one instance. + + Parameters + ---------- + data : pd.DataFrame + Dataframe with 'label' and 'delta' columns, sorted for plotting. + fact_name : str + Name of the predicted class. + foil_name : str + Name of the foil class. + fact_prob : float + Predicted probability of the fact class. + foil_prob : float + Predicted probability of the foil class. + + Returns + ------- + plotly.graph_objs.Figure + The Plotly figure. + """ + import plotly.graph_objs as go + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["delta"] + ] + + fig = go.Figure( + go.Bar( + x=data["delta"], + y=data["label"], + orientation="h", + marker={"color": colors}, + text=data["delta"], + textposition="auto", + ) + ) + + fig.update_layout( + title={ + "text": ( + f"Why {fact_name} (p={fact_prob}) rather than " + f"{foil_name} (p={foil_prob})?" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Attribution difference (fact - foil)"}, + yaxis={"showgrid": True}, + ) + + return fig + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a contrastive bar plot plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's contrastive plot and text summary. + """ + import numpy as np + import pandas as pd + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + max_features = 8 + + groups = [] + for i in exp: + instance = exp[i] + fact_class = instance["fact_class"] + foil_class = instance["foil_class"] + fact_name = target_names[fact_class] + foil_name = target_names[foil_class] + prediction = instance["model_prediction"] + fact_prob = float(np.round(prediction[fact_class], 3)) + foil_prob = float(np.round(prediction[foil_class], 3)) + + data = pd.DataFrame( + { + "features": feature_names, + "values": instance["instance_values"], + "delta": instance["delta_values"], + } + ) + data["delta_abs"] = data["delta"].abs() + data = data.sort_values(by="delta_abs", ascending=True) + if len(data) > max_features: + data = data.iloc[-max_features:, :] + data["label"] = data["features"] + "=" + data["values"].map(str) + + title = f"Instance {int(i) + 1}" + fig = self._create_plot(data, fact_name, foil_name, fact_prob, foil_prob) + plot = PlotlyArtifact(payload=fig) + + top = data.iloc[::-1].head(3) + top_features = ", ".join( + f"{feature}={value}" + for feature, value in zip( + top["features"].tolist(), + top["values"].tolist(), + strict=True, + ) + ) + summary = ( + f"The model predicted {fact_name} (p={fact_prob}) rather than " + f"{foil_name} (p={foil_prob}) mainly because of: " + f"{top_features}." + ) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/dice_counterfactual.py b/DashAI/back/explainability/explainers/dice_counterfactual.py new file mode 100644 index 000000000..381fd6e6a --- /dev/null +++ b/DashAI/back/explainability/explainers/dice_counterfactual.py @@ -0,0 +1,454 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TableArtifact, + TablePayload, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, + string_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class DiceCounterfactualSchema(BaseSchema): + """Schema for the DiCE counterfactual explainer hyperparameters. + + Configures how many counterfactuals are generated, the generation + method and the class the counterfactuals should reach. + """ + + total_cfs: schema_field( + int_field(ge=1, le=10), + placeholder=3, + description=MultilingualString( + en="Number of counterfactual examples to generate per instance.", + es="Número de ejemplos contrafactuales a generar por instancia.", + pt="Número de exemplos contrafactuais a gerar por instância.", + zh="为每个实例生成的反事实示例数量。", + de="Anzahl der pro Instanz erzeugten kontrafaktischen Beispiele.", + ), + alias=MultilingualString( + en="Number of counterfactuals", + es="Número de contrafactuales", + pt="Número de contrafactuais", + zh="反事实数量", + de="Anzahl kontrafaktischer Beispiele", + ), + ) # type: ignore + + method: schema_field( + enum_field(enum=["random", "genetic", "kdtree"]), + placeholder="random", + description=MultilingualString( + en=( + "Counterfactual search strategy: 'random' (random sampling of " + "feature perturbations), 'genetic' (genetic algorithm " + "optimizing proximity and diversity) or 'kdtree' (closest " + "real training examples)." + ), + es=( + "Estrategia de búsqueda: 'random' (muestreo aleatorio de " + "perturbaciones), 'genetic' (algoritmo genético que optimiza " + "proximidad y diversidad) o 'kdtree' (ejemplos reales más " + "cercanos del entrenamiento)." + ), + pt=( + "Estratégia de busca: 'random' (amostragem aleatória de " + "perturbações), 'genetic' (algoritmo genético que otimiza " + "proximidade e diversidade) ou 'kdtree' (exemplos reais mais " + "próximos do treinamento)." + ), + zh=( + "反事实搜索策略:'random'(随机采样特征扰动)、" + "'genetic'(优化接近度和多样性的遗传算法)或'kdtree'(最近的真实训练样本)。" + ), + de=( + "Suchstrategie: 'random' (zufällige Merkmalsstörungen), " + "'genetic' (genetischer Algorithmus für Nähe und Diversität) " + "oder 'kdtree' (nächstgelegene echte Trainingsbeispiele)." + ), + ), + alias=MultilingualString( + en="Search method", + es="Método de búsqueda", + pt="Método de busca", + zh="搜索方法", + de="Suchmethode", + ), + ) # type: ignore + + desired_class: schema_field( + string_field(), + placeholder="opposite", + description=MultilingualString( + en=( + "Class the counterfactuals should reach. Enter an exact class " + "name, or leave 'opposite' to target the runner-up class of " + "each instance." + ), + es=( + "Clase que los contrafactuales deben alcanzar. Ingrese un " + "nombre de clase exacto, o deje 'opposite' para apuntar a la " + "segunda clase más probable de cada instancia." + ), + pt=( + "Classe que os contrafactuais devem alcançar. Insira um nome " + "de classe exato, ou deixe 'opposite' para apontar à segunda " + "classe mais provável de cada instância." + ), + zh="反事实应达到的类别。输入准确的类别名称,或保留'opposite'以针对每个实例的第二可能类别。", + de=( + "Klasse, die die kontrafaktischen Beispiele erreichen sollen. " + "Geben Sie einen exakten Klassennamen ein oder belassen Sie " + "'opposite' für die zweitwahrscheinlichste Klasse." + ), + ), + alias=MultilingualString( + en="Desired class", + es="Clase deseada", + pt="Classe desejada", + zh="目标类别", + de="Zielklasse", + ), + ) # type: ignore + + +class _SklearnProbaShim: + """Adapter exposing the sklearn-native interface DiCE expects. + + DashAI classifiers override ``predict`` to return probabilities; DiCE + expects ``predict`` to return class labels and ``predict_proba`` to + return probabilities. + """ + + def __init__(self, model): + self._model = model + + def predict_proba(self, x): + """Return the class-probability matrix for ``x``.""" + return self._model.predict_proba(x) + + def predict(self, x): + """Return hard class labels derived from the probabilities.""" + import numpy as np + + return np.argmax(self._model.predict_proba(x), axis=1) + + +class DiceCounterfactual(BaseLocalExplainer): + """Diverse counterfactual explanations via the DiCE library. + + For each instance, generates a set of synthetic examples that the model + classifies as a different (desired) class while staying close to the + original instance, answering "what minimal changes would flip this + prediction?". Unlike the Nearest Counterfactual explainer (which returns + real training rows), DiCE synthesizes new feature combinations and + optimizes for both proximity and diversity. + + Note: DiCE queries the underlying estimator directly with raw feature + values, so it is intended for datasets with numeric features. + + References + ---------- + - [1] Mothilal, R.K., Sharma, A. & Tan, C. (2020). "Explaining Machine + Learning Classifiers through Diverse Counterfactual Explanations." + FAT* 2020. https://arxiv.org/abs/1905.07697 + - [2] https://github.com/interpretml/DiCE + """ + + DISPLAY_NAME = MultilingualString( + en="DiCE Counterfactuals", + es="Contrafactuales DiCE", + pt="Contrafactuais DiCE", + zh="DiCE反事实", + de="DiCE-Kontrafaktuale", + ) + DESCRIPTION = MultilingualString( + en=( + "Generates diverse synthetic examples with minimal changes that " + "flip the model's prediction to a desired class." + ), + es=( + "Genera ejemplos sintéticos diversos con cambios mínimos que " + "invierten la predicción del modelo hacia una clase deseada." + ), + pt=( + "Gera exemplos sintéticos diversos com mudanças mínimas que " + "invertem a previsão do modelo para uma classe desejada." + ), + zh="生成具有最小变化的多样化合成示例,将模型预测翻转到目标类别。", + de=( + "Erzeugt diverse synthetische Beispiele mit minimalen Änderungen, " + "die die Modellvorhersage zu einer gewünschten Klasse kippen." + ), + ) + COLOR = "#6A1B9A" + SCHEMA = DiceCounterfactualSchema + + def __init__( + self, + model: BaseModel, + total_cfs: int = 3, + method: str = "random", + desired_class: str = "opposite", + ) -> None: + """Initialize a new instance of a DiceCounterfactual explainer. + + Parameters + ---------- + model : BaseModel + Classification model to be explained. + total_cfs : int + Number of counterfactuals generated per instance. + method : str + DiCE search method: 'random', 'genetic' or 'kdtree'. + desired_class : str + Class name the counterfactuals should reach, or 'opposite' for + the runner-up class of each instance. + """ + super().__init__(model) + self.total_cfs = total_cfs + self.method = method + self.desired_class = desired_class + + def fit(self, background_dataset, **kwargs): + """Build the DiCE data and model interfaces from the train split. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + DiceCounterfactual + The fitted explainer instance (``self``). + """ + import dice_ml + import numpy as np + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = background_dataset + # DiCE samples the training frame and queries the model with plain + # frames, so both must be in the model feature space. + x_train = prepare_model_input(self.model, x["train"]) + y_train = y["train"] + + train_frame = x_train.to_pandas() + self.feature_names = list(train_frame.columns) + + output_column = y_train.column_names[0] + target_names = [str(c) for c in y_train.types[output_column].categories] + self.metadata = { + "feature_names": self.feature_names, + "target_names": target_names, + } + self.output_column = output_column + + labels = y_train.to_pandas()[output_column].astype(str) + encoded = labels.map({name: k for k, name in enumerate(target_names)}) + train_frame = train_frame.copy() + train_frame[output_column] = encoded.to_numpy() + + continuous = [ + column + for column in self.feature_names + if np.issubdtype(train_frame[column].dtype, np.number) + ] + + data_interface = dice_ml.Data( + dataframe=train_frame, + continuous_features=continuous, + outcome_name=output_column, + ) + model_interface = dice_ml.Model( + model=_SklearnProbaShim(self.model), + backend="sklearn", + model_type="classifier", + ) + self._dice = dice_ml.Dice(data_interface, model_interface, method=self.method) + + return self + + def _resolve_desired_class(self, prediction, fact_class: int): + """Resolve DiCE's desired_class argument for one instance. + + Parameters + ---------- + prediction : np.ndarray + Per-class probabilities for the instance. + fact_class : int + Index of the predicted class. + + Returns + ------- + int or str + A class index, or the literal 'opposite' for binary problems. + """ + import numpy as np + + target_names = self.metadata["target_names"] + if self.desired_class in target_names: + desired = target_names.index(self.desired_class) + if desired != fact_class: + return desired + + if len(target_names) == 2: + return "opposite" + order = np.argsort(prediction)[::-1] + return int(order[1]) + + def explain_instance(self, instances): + """Generate counterfactual examples for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction and the + generated counterfactual examples. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + from DashAI.back.explainability.model_input import prepare_model_input + + dataset = to_dashai_dataset(instances) + X = prepare_model_input(self.model, dataset).to_pandas()[self.feature_names] + + predictions = np.asarray(self.model.predict(dataset)) + + explanation = {"metadata": self.metadata} + for i in range(len(X)): + row = X.iloc[[i]] + fact_class = int(np.argmax(predictions[i])) + desired = self._resolve_desired_class(predictions[i], fact_class) + + counterfactuals = [] + try: + result = self._dice.generate_counterfactuals( + row, + total_CFs=self.total_cfs, + desired_class=desired, + ) + cfs_frame = result.cf_examples_list[0].final_cfs_df + if cfs_frame is not None: + for _, cf_row in cfs_frame.iterrows(): + values = [cf_row[f] for f in self.feature_names] + changed = [ + feature + for j, feature in enumerate(self.feature_names) + if not np.isclose(float(values[j]), float(row.iloc[0, j])) + ] + counterfactuals.append( + { + "values": [float(v) for v in values], + "predicted_class": int(cf_row[self.output_column]), + "changed_features": changed, + } + ) + except Exception: # noqa: BLE001 - DiCE may fail to find CFs + counterfactuals = [] + + explanation[i] = { + "instance_values": row.iloc[0].tolist(), + "model_prediction": predictions[i].tolist(), + "predicted_class": fact_class, + "counterfactuals": counterfactuals, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a comparison table plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's comparison table and text summary. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + + groups = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + counterfactuals = instance["counterfactuals"] + + columns = ["Feature", "Instance"] + [ + f"Counterfactual {k + 1}" for k in range(len(counterfactuals)) + ] + rows = [] + highlight = [] + for row_idx, feature in enumerate(feature_names): + row = [feature, instance["instance_values"][row_idx]] + for cf_idx, counterfactual in enumerate(counterfactuals): + row.append(counterfactual["values"][row_idx]) + if feature in counterfactual["changed_features"]: + highlight.append({"row": row_idx, "column": 2 + cf_idx}) + rows.append(row) + + prediction_row = ["Predicted class", predicted_name] + [ + target_names[counterfactual["predicted_class"]] + for counterfactual in counterfactuals + ] + rows.append(prediction_row) + for cf_idx in range(len(counterfactuals)): + highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) + + title = f"Instance {int(i) + 1}" + table = TableArtifact( + payload=TablePayload(columns=columns, rows=rows, highlight=highlight), + ) + + if counterfactuals: + lines = [f"The model predicted {predicted_name} (p={predicted_prob})."] + for cf_idx, counterfactual in enumerate(counterfactuals): + cf_name = target_names[counterfactual["predicted_class"]] + changed = ", ".join(counterfactual["changed_features"]) or "nothing" + lines.append( + f"Counterfactual {cf_idx + 1}: changing {changed} " + f"yields {cf_name}." + ) + summary = "\n".join(lines) + else: + summary = ( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). DiCE could not generate " + "counterfactuals for this instance." + ) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[table, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/grad_cam.py b/DashAI/back/explainability/explainers/grad_cam.py new file mode 100644 index 000000000..d22b95a30 --- /dev/null +++ b/DashAI/back/explainability/explainers/grad_cam.py @@ -0,0 +1,292 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.explainers.image_explainer_utils import ( + get_target_names, + get_torch_module, + get_transform, + heatmap_overlay_artifact, + iter_pil_images, +) +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class GradCamSchema(BaseSchema): + """Schema for the Grad-CAM explainer hyperparameters. + + Configures the CAM variant used to compute the class activation map. + """ + + method: schema_field( + enum_field(enum=["gradcam", "gradcam++", "eigencam"]), + placeholder="gradcam", + description=MultilingualString( + en=( + "CAM variant: 'gradcam' (original), 'gradcam++' (better for " + "multiple occurrences of a class) or 'eigencam' " + "(gradient-free, first principal component of activations)." + ), + es=( + "Variante de CAM: 'gradcam' (original), 'gradcam++' (mejor " + "para múltiples ocurrencias de una clase) o 'eigencam' (sin " + "gradientes, primera componente principal de activaciones)." + ), + pt=( + "Variante de CAM: 'gradcam' (original), 'gradcam++' (melhor " + "para múltiplas ocorrências de uma classe) ou 'eigencam' (sem " + "gradientes, primeira componente principal das ativações)." + ), + zh=( + "CAM变体:'gradcam'(原始)、'gradcam++'(更适合类别多次出现)" + "或'eigencam'(无梯度,激活的第一主成分)。" + ), + de=( + "CAM-Variante: 'gradcam' (Original), 'gradcam++' (besser bei " + "mehrfachem Auftreten einer Klasse) oder 'eigencam' " + "(gradientenfrei, erste Hauptkomponente der Aktivierungen)." + ), + ), + alias=MultilingualString( + en="CAM method", + es="Método CAM", + pt="Método CAM", + zh="CAM方法", + de="CAM-Methode", + ), + ) # type: ignore + + +class GradCam(BaseLocalExplainer): + """Gradient-based class activation maps for image classifiers. + + Grad-CAM backpropagates the score of the predicted class to the last + convolutional layer and weights its activation maps by the averaged + gradients, producing a heatmap of the image regions that most influenced + the prediction. This is a white-box method: it requires a torch module + with a convolutional backbone, so it works with all DashAI image + classifiers except the MLP (use Occlusion Saliency there instead). + + Implemented on top of the ``pytorch-grad-cam`` library. + + References + ---------- + - [1] Selvaraju, R.R. et al. (2017). "Grad-CAM: Visual Explanations from + Deep Networks via Gradient-based Localization." ICCV 2017. + https://arxiv.org/abs/1610.02391 + - [2] https://github.com/jacobgil/pytorch-grad-cam + """ + + DISPLAY_NAME = MultilingualString( + en="Grad-CAM", + es="Grad-CAM", + pt="Grad-CAM", + zh="Grad-CAM", + de="Grad-CAM", + ) + DESCRIPTION = MultilingualString( + en=( + "Highlights the image regions that most influenced the model's " + "prediction using gradient-weighted class activation maps." + ), + es=( + "Resalta las regiones de la imagen que más influyeron en la " + "predicción del modelo usando mapas de activación ponderados por " + "gradientes." + ), + pt=( + "Destaca as regiões da imagem que mais influenciaram a previsão " + "do modelo usando mapas de ativação ponderados por gradientes." + ), + zh="使用梯度加权类激活图突出显示对模型预测影响最大的图像区域。", + de=( + "Hebt die Bildregionen hervor, die die Vorhersage des Modells am " + "stärksten beeinflusst haben, mittels gradientengewichteter " + "Klassenaktivierungskarten." + ), + ) + COLOR = "#C62828" + SCHEMA = GradCamSchema + + def __init__(self, model: BaseModel, method: str = "gradcam") -> None: + """Initialize a new instance of a GradCam explainer. + + Parameters + ---------- + model : BaseModel + Image classification model to be explained. + method : str + CAM variant: 'gradcam', 'gradcam++' or 'eigencam'. + """ + super().__init__(model) + self.method = method + + def fit(self, background_dataset, **kwargs): + """Store class names in the model's class-index order. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + GradCam + The fitted explainer instance (``self``). + """ + _, y = background_dataset + self.metadata = {"target_names": get_target_names(self.model, y)} + return self + + @staticmethod + def _find_target_layer(module): + """Return the last Conv2d layer of the module. + + Parameters + ---------- + module : torch.nn.Module + The model's torch module. + + Returns + ------- + torch.nn.Conv2d + The last convolutional layer. + + Raises + ------ + ValueError + If the module has no convolutional layer. + """ + import torch + + target = None + for layer in module.modules(): + if isinstance(layer, torch.nn.Conv2d): + target = layer + if target is None: + raise ValueError( + "Grad-CAM requires a convolutional backbone, but the model " + "has no Conv2d layer. Use Occlusion Saliency for " + "non-convolutional image models." + ) + return target + + def explain_instance(self, instances): + """Compute a class activation map for each image. + + Parameters + ---------- + instances : DashAIDataset + Images to be explained; the first column must contain images. + + Returns + ------- + dict + Dictionary with, for each image, the resized image, the CAM + heatmap and the model prediction. + """ + import numpy as np + import torch + from pytorch_grad_cam import EigenCAM, GradCAM, GradCAMPlusPlus + from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget + + cam_classes = { + "gradcam": GradCAM, + "gradcam++": GradCAMPlusPlus, + "eigencam": EigenCAM, + } + cam_class = cam_classes[self.method] + + module = get_torch_module(self.model) + target_layer = self._find_target_layer(module) + transform = get_transform(self.model) + image_size = int(getattr(self.model, "image_size", 224)) + device = getattr(self.model, "device", torch.device("cpu")) + + module = module.to(device).eval() + + explanation = {"metadata": self.metadata} + with cam_class(model=module, target_layers=[target_layer]) as cam: + for i, pil_image in enumerate(iter_pil_images(instances)): + tensor = transform(pil_image).unsqueeze(0).to(device) + + with torch.no_grad(): + probs = torch.softmax(module(tensor), dim=1)[0] + predicted_class = int(torch.argmax(probs)) + + grayscale = cam( + input_tensor=tensor, + targets=[ClassifierOutputTarget(predicted_class)], + )[0] + + resized = pil_image.resize((image_size, image_size)) + explanation[i] = { + "image": np.asarray(resized, dtype=np.uint8).tolist(), + "heatmap": np.round(grayscale, 4).tolist(), + "model_prediction": np.round( + probs.detach().cpu().numpy(), 4 + ).tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each image as a heatmap overlay plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained image, each + holding that image's heatmap overlay and text summary. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + groups = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + title = f"Image {int(i) + 1}" + subtitle = ( + f"{self.method}: regions supporting {predicted_name} " + f"(p={predicted_prob})" + ) + overlay = heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle + ) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are the " + "areas whose activations most supported this class." + ), + ) + groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/image_explainer_utils.py b/DashAI/back/explainability/explainers/image_explainer_utils.py new file mode 100644 index 000000000..bba338cd4 --- /dev/null +++ b/DashAI/back/explainability/explainers/image_explainer_utils.py @@ -0,0 +1,175 @@ +"""Shared helpers for image-classification explainers. + +These helpers define the (minimal) white box capability contract image +explainers rely on: + +- ``model.model`` is the underlying ``torch.nn.Module``. +- ``model.get_inference_transform()`` returns the exact transform the + model applies to input images (enforced by the image explainable model + mixins in ``DashAI.back.models.image_explainable_model``). +- ``model.idx_to_label`` maps class indices to label names. +""" + +from typing import Any, List + +from DashAI.back.core.artifacts import PlotlyArtifact + + +def get_torch_module(model: Any): + """Return the underlying ``torch.nn.Module`` of a DashAI image model. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + + Returns + ------- + torch.nn.Module + The trained torch module. + + Raises + ------ + ValueError + If the model does not expose a torch module. + """ + import torch + + module = getattr(model, "model", None) + if module is None or not isinstance(module, torch.nn.Module): + raise ValueError( + "This explainer requires a model exposing its torch module via " + f"the 'model' attribute; got {type(model).__name__}." + ) + return module + + +def get_transform(model: Any): + """Return the model's inference transform. + + Models compatible with image explainers implement + ``get_inference_transform`` (enforced by the + ``OcclusionSaliencyCompatibleModel`` / ``GradCamCompatibleModel`` + mixins), exposing the exact preprocessing they apply to input images. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + + Returns + ------- + Callable + A transform mapping a PIL image to a normalized tensor. + + Raises + ------ + ValueError + If the model does not implement ``get_inference_transform``. + """ + transform_factory = getattr(model, "get_inference_transform", None) + if transform_factory is None: + raise ValueError( + "This explainer requires a model implementing " + "'get_inference_transform' (see the image explainable model " + f"mixins); got {type(model).__name__}." + ) + return transform_factory() + + +def get_target_names(model: Any, y_dataset) -> List[str]: + """Resolve class names in the model's class-index order. + + Prefers the model's ``idx_to_label`` mapping (which reflects the label + order used at training time) and falls back to the sorted categories of + the target column. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + y_dataset : Any + Target splits; ``y_dataset["train"]`` must expose ``column_names`` + and ``types``. + + Returns + ------- + List[str] + Class names indexed by model output position. + """ + idx_to_label = getattr(model, "idx_to_label", None) + if idx_to_label: + return [str(idx_to_label[key]) for key in sorted(idx_to_label)] + + y_train = y_dataset["train"] + output_column = y_train.column_names[0] + return sorted(str(c) for c in y_train.types[output_column].categories) + + +def iter_pil_images(instances): + """Yield the PIL image of each row in an image dataset. + + Parameters + ---------- + instances : Any + A DashAIDataset (or compatible) whose first column holds images + exposing ``to_pil()``. + + Yields + ------ + PIL.Image.Image + Each image converted to RGB. + """ + image_column = list(instances.features.keys())[0] + for index in range(len(instances)): + yield instances[index][image_column].to_pil().convert("RGB") + + +def heatmap_overlay_artifact( + image_uint8, + heatmap, + title: str, + subtitle: str, +) -> PlotlyArtifact: + """Build a plotly artifact with a jet heatmap blended over an image. + + Parameters + ---------- + image_uint8 : array like + RGB image of shape (H, W, 3), uint8 values. + heatmap : array like + Saliency map of shape (H, W) with values in [0, 1]. + title : str + Artifact title (shown in the instance selector). + subtitle : str + Figure title (e.g. predicted class and probability). + + Returns + ------- + PlotlyArtifact + The plotly artifact with the blended overlay figure. + """ + import numpy as np + import plotly.graph_objs as go + + image = np.asarray(image_uint8, dtype=np.float32) / 255.0 + cam = np.clip(np.asarray(heatmap, dtype=np.float32), 0.0, 1.0) + + # Jet like colormap, avoids a matplotlib/cv2 dependency at plot time. + red = np.clip(1.5 - np.abs(4 * cam - 3), 0, 1) + green = np.clip(1.5 - np.abs(4 * cam - 2), 0, 1) + blue = np.clip(1.5 - np.abs(4 * cam - 1), 0, 1) + colored = np.stack([red, green, blue], axis=-1) + + blended = (0.5 * image + 0.5 * colored) * 255.0 + blended = blended.astype(np.uint8) + + fig = go.Figure(go.Image(z=blended)) + fig.update_layout( + title={"text": subtitle, "font": {"size": 14}}, + margin={"l": 10, "r": 10, "t": 50, "b": 10}, + xaxis={"visible": False}, + yaxis={"visible": False}, + ) + + return PlotlyArtifact(payload=fig, title=title) diff --git a/DashAI/back/explainability/explainers/kernel_shap.py b/DashAI/back/explainability/explainers/kernel_shap.py index a02d6d230..69b0eb738 100644 --- a/DashAI/back/explainability/explainers/kernel_shap.py +++ b/DashAI/back/explainability/explainers/kernel_shap.py @@ -1,3 +1,10 @@ +from typing import List, Optional + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, @@ -326,9 +333,13 @@ def fit( """ sample_background_data = bool(sample_background_data) + from DashAI.back.explainability.model_input import prepare_model_input + x, y = background_dataset - x_train = x["train"] + # SHAP perturbs the background frame and calls the model with it, so + # the background must already be in the model's feature space. + x_train = prepare_model_input(self.model, x["train"]) y_train = y["train"] background_data = x_train.to_pandas() @@ -385,13 +396,10 @@ def explain_instance( dictionary with the shap values for each instance. """ from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + from DashAI.back.explainability.model_input import prepare_model_input dataset_dashai = to_dashai_dataset(instances) - - if hasattr(self.model, "prepare_dataset"): - dataset_prepared = self.model.prepare_dataset(dataset_dashai, is_fit=False) - else: - dataset_prepared = dataset_dashai + dataset_prepared = prepare_model_input(self.model, dataset_dashai) X = dataset_prepared.to_pandas() @@ -424,7 +432,12 @@ def explain_instance( return explanation def _create_plot( - self, data, base_value: float, y_pred_pbb: float, y_pred_name: str + self, + data, + base_value: float, + y_pred_pbb: float, + y_pred_name: str, + title: Optional[str] = None, ): """Helper method to create the explanation plot using plotly. @@ -438,15 +451,16 @@ def _create_plot( predicted probability. y_pred_name name of the predicted class. + title: Optional[str] + title of the resulting artifact. - Returns: - JSON - JSON containing the information of the explanation plot - to be rendered. + Returns + ------- + PlotlyArtifact + The plotly artifact of the explanation plot for one instance. """ # Lazy imports import numpy as np - import plotly import plotly.graph_objs as go x = data["shap_values"].to_numpy() @@ -509,20 +523,21 @@ def _create_plot( y=-0.27, ) - return plotly.io.to_json(fig) + return PlotlyArtifact(payload=fig, title=title) - def plot(self, explanation: list[dict]): - """Method to create the explanation plot using plotly. + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Method to create the explanation plots using plotly. Parameters ---------- - explanation: dict - dictionary with the explanation generated by the explainer. + explanation : dict + Dictionary with the explanation generated by the explainer. - Returns: - List[dict] - list of JSONs containing the information of the explanation plot - to be rendered. + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group ("Instance 1", ...) per + explained instance, each holding that instance's plotly plot. """ exp = explanation.copy() @@ -540,8 +555,8 @@ def plot(self, explanation: list[dict]): feats = np.asarray(feature_names, dtype=str).reshape(-1) - plots = [] - for i in exp: + groups = [] + for instance_number, i in enumerate(exp, start=1): instance_values = exp[i]["instance_values"] model_prediction = exp[i]["model_prediction"] y_pred_class = int(np.argmax(model_prediction)) @@ -641,7 +656,14 @@ def plot(self, explanation: list[dict]): else: base_value = float(base_arr[y_pred_class]) - plot = self._create_plot(data, base_value, y_pred_pbb, y_pred_name) - plots.append(plot) + plot = self._create_plot( + data, + base_value, + y_pred_pbb, + y_pred_name, + ) + groups.append( + ArtifactGroup(title=f"Instance {instance_number}", artifacts=[plot]) + ) - return plots + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/lime_text.py b/DashAI/back/explainability/explainers/lime_text.py new file mode 100644 index 000000000..41fdd8e74 --- /dev/null +++ b/DashAI/back/explainability/explainers/lime_text.py @@ -0,0 +1,322 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class LimeTextSchema(BaseSchema): + """Schema for the LIME text explainer hyperparameters. + + Configures how many words are reported and how many perturbed samples + LIME draws to fit its local surrogate model. + """ + + num_features: schema_field( + int_field(ge=1, le=50), + placeholder=10, + description=MultilingualString( + en="Maximum number of words reported in the explanation.", + es="Número máximo de palabras reportadas en la explicación.", + pt="Número máximo de palavras reportadas na explicação.", + zh="解释中报告的最大词数。", + de="Maximale Anzahl der in der Erklärung gemeldeten Wörter.", + ), + alias=MultilingualString( + en="Number of words", + es="Número de palabras", + pt="Número de palavras", + zh="词数", + de="Anzahl der Wörter", + ), + ) # type: ignore + + num_samples: schema_field( + int_field(ge=100, le=5000), + placeholder=1000, + description=MultilingualString( + en=( + "Number of perturbed texts sampled to fit the local surrogate " + "model. More samples give more stable explanations but take " + "longer." + ), + es=( + "Número de textos perturbados muestreados para ajustar el " + "modelo sustituto local. Más muestras dan explicaciones más " + "estables pero tardan más." + ), + pt=( + "Número de textos perturbados amostrados para ajustar o " + "modelo substituto local. Mais amostras dão explicações mais " + "estáveis, mas demoram mais." + ), + zh="为拟合局部代理模型而采样的扰动文本数量。样本越多解释越稳定,但耗时越长。", + de=( + "Anzahl der gestörten Texte zum Anpassen des lokalen " + "Ersatzmodells. Mehr Stichproben ergeben stabilere " + "Erklärungen, dauern aber länger." + ), + ), + alias=MultilingualString( + en="Number of samples", + es="Número de muestras", + pt="Número de amostras", + zh="样本数量", + de="Anzahl der Stichproben", + ), + ) # type: ignore + + +class LimeText(BaseLocalExplainer): + """LIME explanations for text classification models. + + Fits a sparse linear surrogate model on random word-masked variants of + the input text, weighting variants by similarity to the original. The + surrogate's coefficients estimate each word's contribution to the + predicted class. Model agnostic: only ``predict`` is queried. Compared to + Token Ablation (one word at a time), LIME captures joint effects of + removing several words but is stochastic and needs more model calls. + + References + ---------- + - [1] Ribeiro, M.T., Singh, S. & Guestrin, C. (2016). "'Why Should I + Trust You?' Explaining the Predictions of Any Classifier." + KDD 2016. https://arxiv.org/abs/1602.04938 + - [2] https://github.com/marcotcr/lime + """ + + COMPATIBLE_COMPONENTS = ["TextClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="LIME (text)", + es="LIME (texto)", + pt="LIME (texto)", + zh="LIME(文本)", + de="LIME (Text)", + ) + DESCRIPTION = MultilingualString( + en=( + "Fits a local linear surrogate on word-masked text variants to " + "estimate each word's contribution to the prediction." + ), + es=( + "Ajusta un sustituto lineal local sobre variantes del texto con " + "palabras enmascaradas para estimar la contribución de cada " + "palabra a la predicción." + ), + pt=( + "Ajusta um substituto linear local em variantes do texto com " + "palavras mascaradas para estimar a contribuição de cada palavra " + "à previsão." + ), + zh="在词遮蔽的文本变体上拟合局部线性代理模型,以估计每个词对预测的贡献。", + de=( + "Passt ein lokales lineares Ersatzmodell auf wortmaskierten " + "Textvarianten an, um den Beitrag jedes Wortes zur Vorhersage zu " + "schätzen." + ), + ) + COLOR = "#2E7D32" + SCHEMA = LimeTextSchema + + def __init__( + self, + model: BaseModel, + num_features: int = 10, + num_samples: int = 1000, + ) -> None: + """Initialize a new instance of a LimeText explainer. + + Parameters + ---------- + model : BaseModel + Text classification model to be explained. + num_features : int + Maximum number of words reported per explanation. + num_samples : int + Number of perturbed texts sampled by LIME. + """ + super().__init__(model) + self.num_features = num_features + self.num_samples = num_samples + + def fit(self, background_dataset, **kwargs): + """Store class names from the training targets. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + LimeText + The fitted explainer instance (``self``). + """ + _, y = background_dataset + y_train = y["train"] + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = {"target_names": [str(c) for c in target_names]} + + return self + + def explain_instance(self, instances): + """Compute LIME word attributions for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained; must contain a single text column + (tokenizer artifact columns are ignored). + + Returns + ------- + dict + Dictionary with, for each instance, the word weights and the + model prediction. + """ + import numpy as np + import pandas as pd + from lime.lime_text import LimeTextExplainer + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + # Same guard as TokenAblation: the job may hand over a dataset the + # model already prepared (tokenized), so rebuild from raw text only. + tokenizer_columns = {"input_ids", "attention_mask", "token_type_ids", "label"} + text_columns = [c for c in X.columns if c not in tokenizer_columns] + if not text_columns: + raise ValueError(f"No text column found among columns: {list(X.columns)}") + text_column = text_columns[0] + texts = X[text_column].astype(str).tolist() + + def classifier_fn(variant_texts): + variants_dataset = to_dashai_dataset( + pd.DataFrame({text_column: list(variant_texts)}) + ) + return np.asarray(self.model.predict(variants_dataset)) + + base_dataset = to_dashai_dataset(pd.DataFrame({text_column: texts})) + base_predictions = np.asarray(self.model.predict(base_dataset)) + + target_names = self.metadata["target_names"] + lime_explainer = LimeTextExplainer(class_names=target_names, random_state=0) + + explanation = {"metadata": {**self.metadata, "text_column": text_column}} + for i, text in enumerate(texts): + predicted_class = int(np.argmax(base_predictions[i])) + + lime_result = lime_explainer.explain_instance( + text, + classifier_fn, + labels=(predicted_class,), + num_features=self.num_features, + num_samples=self.num_samples, + ) + word_weights = [ + [word, float(np.round(weight, 4))] + for word, weight in lime_result.as_list(label=predicted_class) + ] + + explanation[i] = { + "text": text, + "word_weights": word_weights, + "model_prediction": base_predictions[i].tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a word weight bar plot plus a summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's word weight plot and text summary. + """ + import numpy as np + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + groups = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + word_weights = sorted( + instance["word_weights"], key=lambda pair: abs(pair[1]) + ) + + words = [pair[0] for pair in word_weights] + weights = [pair[1] for pair in word_weights] + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in weights + ] + fig = go.Figure( + go.Bar( + x=weights, + y=words, + orientation="h", + marker={"color": colors}, + text=weights, + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"LIME word weights for {predicted_name} (p={predicted_prob})" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Weight (towards predicted class)"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + plot = PlotlyArtifact(payload=fig) + + top = list(reversed(word_weights))[:3] + top_words = ", ".join(f"'{word}' ({weight:+})" for word, weight in top) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Most influential words: " + f"{top_words}." + ), + ) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/nearest_counterfactual.py b/DashAI/back/explainability/explainers/nearest_counterfactual.py new file mode 100644 index 000000000..948d5b88b --- /dev/null +++ b/DashAI/back/explainability/explainers/nearest_counterfactual.py @@ -0,0 +1,416 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TableArtifact, + TablePayload, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class NearestCounterfactualSchema(BaseSchema): + """Schema for NearestCounterfactual explainer hyperparameters. + + Configures how many counterfactual examples are retrieved per instance and + the distance metric used to rank candidate examples. + """ + + n_counterfactuals: schema_field( + int_field(ge=1, le=10), + placeholder=3, + description=MultilingualString( + en=( + "Number of counterfactual examples to retrieve for each " + "instance. Each counterfactual is a real training example " + "that the model classifies differently." + ), + es=( + "Número de ejemplos contrafactuales a recuperar por cada " + "instancia. Cada contrafactual es un ejemplo real de " + "entrenamiento que el modelo clasifica de forma distinta." + ), + pt=( + "Número de exemplos contrafactuais a recuperar para cada " + "instância. Cada contrafactual é um exemplo real de " + "treinamento que o modelo classifica de forma diferente." + ), + zh=( + "为每个实例检索的反事实示例数量。每个反事实都是模型分类不同的真实训练样本。" + ), + de=( + "Anzahl der kontrafaktischen Beispiele pro Instanz. Jedes " + "kontrafaktische Beispiel ist ein echtes Trainingsbeispiel, " + "das das Modell anders klassifiziert." + ), + ), + alias=MultilingualString( + en="Number of counterfactuals", + es="Número de contrafactuales", + pt="Número de contrafactuais", + zh="反事实数量", + de="Anzahl kontrafaktischer Beispiele", + ), + ) # type: ignore + + distance: schema_field( + enum_field(enum=["l1", "l2"]), + placeholder="l1", + description=MultilingualString( + en=( + "Distance used to rank candidate counterfactuals. Numeric " + "features are normalized by their range; non-numeric features " + "add a constant penalty when they differ." + ), + es=( + "Distancia usada para ordenar los contrafactuales candidatos. " + "Las características numéricas se normalizan por su rango; " + "las no numéricas agregan una penalización constante cuando " + "difieren." + ), + pt=( + "Distância usada para ordenar os contrafactuais candidatos. " + "As características numéricas são normalizadas pelo seu " + "intervalo; as não numéricas adicionam uma penalização " + "constante quando diferem." + ), + zh=( + "用于对候选反事实排序的距离。数值特征按范围归一化;非数值特征在不同时增加固定惩罚。" + ), + de=( + "Distanz zur Rangordnung der kontrafaktischen Kandidaten. " + "Numerische Merkmale werden über ihren Wertebereich " + "normalisiert; nicht numerische Merkmale erhalten bei " + "Abweichung eine konstante Strafe." + ), + ), + alias=MultilingualString( + en="Distance metric", + es="Métrica de distancia", + pt="Métrica de distância", + zh="距离度量", + de="Distanzmetrik", + ), + ) # type: ignore + + +class NearestCounterfactual(BaseLocalExplainer): + """Case-based counterfactual explainer for tabular classification. + + For each instance to explain, this explainer answers "what would have to + be different for the model to predict another class?" by retrieving the + nearest real training examples that the model classifies differently + (nearest unlike neighbors). Because counterfactuals are actual dataset + rows, they are always plausible and never out of distribution, unlike + synthetic perturbation-based counterfactuals. + + The explainer is fully model agnostic: it only queries ``predict``. + + References + ---------- + - [1] Wachter, S., Mittelstadt, B. & Russell, C. (2017). "Counterfactual + Explanations without Opening the Black Box." Harvard JOLT 31(2). + https://arxiv.org/abs/1711.00399 + - [2] Keane, M.T. & Smyth, B. (2020). "Good Counterfactuals and Where to + Find Them." ICCBR 2020. https://arxiv.org/abs/2005.13997 + """ + + COMPATIBLE_COMPONENTS = ["TabularClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Nearest Counterfactual", + es="Contrafactual más cercano", + pt="Contrafactual mais próximo", + zh="最近反事实", + de="Nächstes kontrafaktisches Beispiel", + ) + DESCRIPTION = MultilingualString( + en=( + "Finds the closest real examples classified differently by the " + "model, showing which feature changes would flip the prediction." + ), + es=( + "Encuentra los ejemplos reales más cercanos clasificados de forma " + "distinta por el modelo, mostrando qué cambios de características " + "invertirían la predicción." + ), + pt=( + "Encontra os exemplos reais mais próximos classificados de forma " + "diferente pelo modelo, mostrando quais mudanças de " + "características inverteriam a previsão." + ), + zh=("查找模型分类不同的最近真实示例,展示哪些特征变化会翻转预测。"), + de=( + "Findet die nächstgelegenen realen Beispiele, die das Modell " + "anders klassifiziert, und zeigt, welche Merkmalsänderungen die " + "Vorhersage kippen würden." + ), + ) + COLOR = "#7B1FA2" + SCHEMA = NearestCounterfactualSchema + + def __init__( + self, + model: BaseModel, + n_counterfactuals: int = 3, + distance: str = "l1", + ) -> None: + """Initialize a new instance of a NearestCounterfactual explainer. + + Parameters + ---------- + model : BaseModel + Model to be explained. + n_counterfactuals : int + Number of counterfactual examples retrieved per instance. + distance : str + Distance used to rank candidates: 'l1' or 'l2'. + """ + super().__init__(model) + self.n_counterfactuals = n_counterfactuals + self.distance = distance + + def fit(self, background_dataset, **kwargs): + """Store the background data and its model predictions. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. The train split is used + as the pool of counterfactual candidates. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + NearestCounterfactual + The fitted explainer instance (``self``). + """ + import numpy as np + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = background_dataset + # Candidates and explained instances are compared feature by feature, + # so both are kept in the model feature space. predict() keeps taking + # the raw split, since it applies that preparation itself. + x_train = x["train"] + x_train_prepared = prepare_model_input(self.model, x_train) + y_train = y["train"] + + self.background_data = x_train_prepared.to_pandas() + self.feature_names = list(x_train_prepared.column_names) + + background_probs = self.model.predict(x_train) + self.background_classes = np.argmax(np.asarray(background_probs), axis=1) + + # Per-feature range for numeric columns, used to normalize distances. + self._numeric_columns = [ + column + for column in self.background_data.columns + if np.issubdtype(self.background_data[column].dtype, np.number) + ] + ranges = {} + for column in self._numeric_columns: + column_range = float( + self.background_data[column].max() - self.background_data[column].min() + ) + ranges[column] = column_range if column_range > 0 else 1.0 + self._ranges = ranges + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = { + "feature_names": self.feature_names, + "target_names": list(target_names), + } + + return self + + def _distances(self, instance_row, candidates): + """Compute normalized distances between one instance and candidates. + + Parameters + ---------- + instance_row : pd.Series + The instance to explain. + candidates : pd.DataFrame + Candidate counterfactual rows. + + Returns + ------- + np.ndarray + One distance per candidate row. + """ + import numpy as np + + total = np.zeros(len(candidates), dtype=float) + for column in candidates.columns: + if column in self._ranges: + diff = ( + np.abs( + candidates[column].to_numpy(dtype=float) + - float(instance_row[column]) + ) + / self._ranges[column] + ) + total += diff if self.distance == "l1" else diff**2 + else: + mismatch = ( + candidates[column].to_numpy() != instance_row[column] + ).astype(float) + total += mismatch + + return np.sqrt(total) if self.distance == "l2" else total + + def explain_instance(self, instances): + """Retrieve the nearest counterfactual examples for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction and the + retrieved counterfactual examples. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + from DashAI.back.explainability.model_input import prepare_model_input + + dataset = to_dashai_dataset(instances) + X = prepare_model_input(self.model, dataset).to_pandas() + + predictions = np.asarray(self.model.predict(dataset)) + + explanation = {"metadata": self.metadata} + for i, (_, instance_row) in enumerate(X.iterrows()): + predicted_class = int(np.argmax(predictions[i])) + + candidate_mask = self.background_classes != predicted_class + candidates = self.background_data[candidate_mask] + + counterfactuals = [] + if len(candidates) > 0: + distances = self._distances(instance_row, candidates) + order = np.argsort(distances)[: self.n_counterfactuals] + for rank in order: + row = candidates.iloc[int(rank)] + changed_features = [ + feature + for feature in self.feature_names + if row[feature] != instance_row[feature] + ] + candidate_index = int(candidates.index[int(rank)]) + counterfactuals.append( + { + "values": row.tolist(), + "predicted_class": int( + self.background_classes[ + self.background_data.index.get_loc(candidate_index) + ] + ), + "distance": float(np.round(distances[int(rank)], 4)), + "changed_features": changed_features, + } + ) + + explanation[i] = { + "instance_values": instance_row.tolist(), + "model_prediction": predictions[i].tolist(), + "predicted_class": predicted_class, + "counterfactuals": counterfactuals, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a comparison table plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's comparison table and text summary. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + + groups = [] + for i in exp: + instance = exp[i] + instance_values = instance["instance_values"] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + counterfactuals = instance["counterfactuals"] + + columns = ["Feature", "Instance"] + [ + f"Counterfactual {k + 1}" for k in range(len(counterfactuals)) + ] + rows = [] + highlight = [] + for row_idx, feature in enumerate(feature_names): + row = [feature, instance_values[row_idx]] + for cf_idx, counterfactual in enumerate(counterfactuals): + row.append(counterfactual["values"][row_idx]) + if feature in counterfactual["changed_features"]: + highlight.append({"row": row_idx, "column": 2 + cf_idx}) + rows.append(row) + + prediction_row = ["Predicted class", predicted_name] + [ + target_names[counterfactual["predicted_class"]] + for counterfactual in counterfactuals + ] + rows.append(prediction_row) + for cf_idx in range(len(counterfactuals)): + highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) + + title = f"Instance {int(i) + 1}" + table = TableArtifact( + payload=TablePayload(columns=columns, rows=rows, highlight=highlight), + ) + + if counterfactuals: + lines = [f"The model predicted {predicted_name} (p={predicted_prob})."] + for cf_idx, counterfactual in enumerate(counterfactuals): + cf_name = target_names[counterfactual["predicted_class"]] + changed = ", ".join(counterfactual["changed_features"]) or "nothing" + lines.append( + f"Counterfactual {cf_idx + 1}: changing {changed} " + f"yields {cf_name} " + f"(distance {counterfactual['distance']})." + ) + summary = "\n".join(lines) + else: + summary = ( + f"The model predicted {predicted_name} (p={predicted_prob}). " + "No counterfactual examples were found in the training data." + ) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[table, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/occlusion_saliency.py b/DashAI/back/explainability/explainers/occlusion_saliency.py new file mode 100644 index 000000000..5baa6a18d --- /dev/null +++ b/DashAI/back/explainability/explainers/occlusion_saliency.py @@ -0,0 +1,347 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.explainers.image_explainer_utils import ( + get_target_names, + get_torch_module, + get_transform, + heatmap_overlay_artifact, + iter_pil_images, +) +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class OcclusionSaliencySchema(BaseSchema): + """Schema for the Occlusion Saliency explainer hyperparameters. + + Configures the size and stride of the occlusion patch, in pixels of the + model's input resolution. + """ + + patch_size: schema_field( + int_field(ge=4, le=128), + placeholder=16, + description=MultilingualString( + en=( + "Side (in pixels) of the square patch that is occluded at " + "each position. Smaller patches give finer maps but require " + "more model evaluations." + ), + es=( + "Lado (en píxeles) del parche cuadrado que se ocluye en cada " + "posición. Parches más pequeños dan mapas más finos pero " + "requieren más evaluaciones del modelo." + ), + pt=( + "Lado (em pixels) do patch quadrado ocluído em cada posição. " + "Patches menores dão mapas mais finos, mas requerem mais " + "avaliações do modelo." + ), + zh="每个位置遮挡的正方形补丁的边长(像素)。较小的补丁产生更精细的图,但需要更多模型评估。", + de=( + "Seitenlänge (in Pixeln) des quadratischen Patches, der an " + "jeder Position verdeckt wird. Kleinere Patches ergeben " + "feinere Karten, erfordern aber mehr Modellauswertungen." + ), + ), + alias=MultilingualString( + en="Patch size", + es="Tamaño del parche", + pt="Tamanho do patch", + zh="补丁大小", + de="Patchgröße", + ), + ) # type: ignore + + stride: schema_field( + int_field(ge=2, le=64), + placeholder=8, + description=MultilingualString( + en=( + "Step (in pixels) between consecutive patch positions. " + "Smaller strides give smoother maps but require more model " + "evaluations." + ), + es=( + "Paso (en píxeles) entre posiciones consecutivas del parche. " + "Pasos más pequeños dan mapas más suaves pero requieren más " + "evaluaciones del modelo." + ), + pt=( + "Passo (em pixels) entre posições consecutivas do patch. " + "Passos menores dão mapas mais suaves, mas requerem mais " + "avaliações do modelo." + ), + zh="连续补丁位置之间的步长(像素)。较小的步长产生更平滑的图,但需要更多模型评估。", + de=( + "Schrittweite (in Pixeln) zwischen aufeinanderfolgenden " + "Patchpositionen. Kleinere Schritte ergeben glattere Karten, " + "erfordern aber mehr Modellauswertungen." + ), + ), + alias=MultilingualString( + en="Stride", + es="Paso", + pt="Passo", + zh="步长", + de="Schrittweite", + ), + ) # type: ignore + + +class OcclusionSaliency(BaseLocalExplainer): + """Perturbation-based saliency maps for image classifiers. + + Slides a gray patch over the image and records how much the predicted + class probability drops at each position. Regions whose occlusion causes + a large drop are the ones the model relied on. Unlike Grad-CAM, this + method needs no gradients or convolutional layers, so it works with every + DashAI image classifier including the MLP; the trade-off is one model + evaluation per patch position. + + References + ---------- + - [1] Zeiler, M.D. & Fergus, R. (2014). "Visualizing and Understanding + Convolutional Networks." ECCV 2014. https://arxiv.org/abs/1311.2901 + """ + + DISPLAY_NAME = MultilingualString( + en="Occlusion Saliency", + es="Saliencia por oclusión", + pt="Saliência por oclusão", + zh="遮挡显著性", + de="Okklusions-Salienz", + ) + DESCRIPTION = MultilingualString( + en=( + "Slides a gray patch over the image and maps how much each " + "region's occlusion lowers the predicted class probability." + ), + es=( + "Desliza un parche gris sobre la imagen y mapea cuánto baja la " + "probabilidad de la clase predicha al ocluir cada región." + ), + pt=( + "Desliza um patch cinza sobre a imagem e mapeia o quanto a " + "oclusão de cada região reduz a probabilidade da classe prevista." + ), + zh="在图像上滑动灰色补丁,映射遮挡每个区域对预测类别概率的降低程度。", + de=( + "Schiebt einen grauen Patch über das Bild und kartiert, wie stark " + "die Verdeckung jeder Region die vorhergesagte " + "Klassenwahrscheinlichkeit senkt." + ), + ) + COLOR = "#AD1457" + SCHEMA = OcclusionSaliencySchema + + def __init__( + self, + model: BaseModel, + patch_size: int = 16, + stride: int = 8, + ) -> None: + """Initialize a new instance of an OcclusionSaliency explainer. + + Parameters + ---------- + model : BaseModel + Image classification model to be explained. + patch_size : int + Side of the occluded square patch, in pixels. + stride : int + Step between consecutive patch positions, in pixels. + """ + super().__init__(model) + self.patch_size = patch_size + self.stride = stride + + def fit(self, background_dataset, **kwargs): + """Store class names in the model's class-index order. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + OcclusionSaliency + The fitted explainer instance (``self``). + """ + _, y = background_dataset + self.metadata = {"target_names": get_target_names(self.model, y)} + return self + + def _occlusion_map(self, module, tensor, predicted_class, device): + """Compute the probability-drop map for one image tensor. + + Parameters + ---------- + module : torch.nn.Module + The model's torch module in eval mode. + tensor : torch.Tensor + Input tensor of shape (1, C, H, W). + predicted_class : int + Class whose probability drop is measured. + device : torch.device + Device to run the evaluations on. + + Returns + ------- + np.ndarray + Saliency map of shape (H, W), normalized to [0, 1]. + """ + import numpy as np + import torch + + _, _, height, width = tensor.shape + baseline = tensor.mean(dim=(2, 3), keepdim=True) + + with torch.no_grad(): + base_prob = torch.softmax(module(tensor), dim=1)[0, predicted_class] + base_prob = float(base_prob) + + positions = [ + (top, left) + for top in range(0, max(height - self.patch_size, 0) + 1, self.stride) + for left in range(0, max(width - self.patch_size, 0) + 1, self.stride) + ] + + drops = np.zeros((height, width), dtype=np.float32) + counts = np.zeros((height, width), dtype=np.float32) + + batch_size = 32 + with torch.no_grad(): + for start in range(0, len(positions), batch_size): + batch_positions = positions[start : start + batch_size] + occluded = tensor.repeat(len(batch_positions), 1, 1, 1) + for j, (top, left) in enumerate(batch_positions): + occluded[ + j, + :, + top : top + self.patch_size, + left : left + self.patch_size, + ] = baseline[0] + probs = torch.softmax(module(occluded.to(device)), dim=1)[ + :, predicted_class + ] + for j, (top, left) in enumerate(batch_positions): + drop = base_prob - float(probs[j]) + drops[ + top : top + self.patch_size, + left : left + self.patch_size, + ] += drop + counts[ + top : top + self.patch_size, + left : left + self.patch_size, + ] += 1.0 + + saliency = drops / np.maximum(counts, 1.0) + saliency = np.clip(saliency, 0.0, None) + max_value = saliency.max() + if max_value > 0: + saliency = saliency / max_value + return saliency + + def explain_instance(self, instances): + """Compute an occlusion saliency map for each image. + + Parameters + ---------- + instances : DashAIDataset + Images to be explained; the first column must contain images. + + Returns + ------- + dict + Dictionary with, for each image, the resized image, the saliency + map and the model prediction. + """ + import numpy as np + import torch + + module = get_torch_module(self.model) + transform = get_transform(self.model) + image_size = int(getattr(self.model, "image_size", 224)) + device = getattr(self.model, "device", torch.device("cpu")) + + module = module.to(device).eval() + + explanation = {"metadata": self.metadata} + for i, pil_image in enumerate(iter_pil_images(instances)): + tensor = transform(pil_image).unsqueeze(0).to(device) + + with torch.no_grad(): + probs = torch.softmax(module(tensor), dim=1)[0] + predicted_class = int(torch.argmax(probs)) + + saliency = self._occlusion_map(module, tensor, predicted_class, device) + + resized = pil_image.resize((image_size, image_size)) + explanation[i] = { + "image": np.asarray(resized, dtype=np.uint8).tolist(), + "heatmap": np.round(saliency, 4).tolist(), + "model_prediction": np.round(probs.detach().cpu().numpy(), 4).tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each image as a saliency overlay plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained image, each + holding that image's saliency overlay and text summary. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + groups = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + title = f"Image {int(i) + 1}" + subtitle = f"Occlusion saliency for {predicted_name} (p={predicted_prob})" + overlay = heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle + ) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are those " + "whose occlusion most lowered that probability." + ), + ) + groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/partial_dependence.py b/DashAI/back/explainability/explainers/partial_dependence.py index 89079e38c..b2289fa4e 100644 --- a/DashAI/back/explainability/explainers/partial_dependence.py +++ b/DashAI/back/explainability/explainers/partial_dependence.py @@ -1,5 +1,10 @@ from typing import List +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, float_field, @@ -201,13 +206,19 @@ def explain(self, dataset): import numpy as np from sklearn.inspection import partial_dependence + from DashAI.back.explainability.model_input import prepare_model_input + x, y = dataset - x_test = x["test"].to_pandas() + # scikit-learn's partial_dependence calls the model with plain frames, + # bypassing the model preparation, so both splits are moved into the + # model feature space here. + x_test_dataset = prepare_model_input(self.model, x["test"]) + x_test = x_test_dataset.to_pandas() - types = x["train"].types + types = prepare_model_input(self.model, x["train"]).types - features_names = x["test"].column_names + features_names = x_test_dataset.column_names categorical_features = [ 1 if isinstance(types[feature], Categorical) else 0 @@ -243,90 +254,60 @@ def explain(self, dataset): return explanation - def _create_plot(self, data: List[object]) -> List[dict]: - """Helper method to create the explanation plot using plotly. + def _create_plot(self, data: List[object]) -> List[GroupedArtifacts]: + """Helper method to create the explanation plots using plotly. + + Bundles one group per feature and class curve into a single grouped + artifact, so the frontend renders a selector over the curves instead of + a dropdown embedded in a single figure. Parameters ---------- - data: List - dictionary with the explanation generated by the explainer. - - Returns: - List[dict] - list of JSON containing the information of the explanation plot - to be rendered. + data : List + Per feature and class DataFrames with the explanation generated by + the explainer. Each DataFrame has the curve values in its first + column and the grid positions in ``"grid_values"``. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group (a plotly curve) per + feature and class. """ # Lazy imports - import plotly import plotly.express as px - fig = px.line( - data[0], - x=data[0]["grid_values"], - y=data[0].iloc[:, 0], - labels={"grid_values": "Feature value"}, - ) - - fig.update_layout( - yaxis_title="Partial Dependence", - updatemenus=[ - { - "x": 0, - "xanchor": "left", - "y": 1.2, - "yanchor": "top", - "buttons": [ - { - "label": data[i].columns[0], - "method": "restyle", - "args": [ - { - "x": [data[i]["grid_values"]], - "y": [data[i].iloc[:, 0]], - }, - ], - } - for i in range(len(data)) - ], - } - ], - ) - - plot_note = ( - "This graph shows the marginal effect of the selected feature " - "on the
probability predicted by the model for the selected " - "class" - ) - - fig.add_annotation( - align="center", - arrowsize=0.3, - arrowwidth=0.1, - borderwidth=2, - font={"size": 12}, - showarrow=False, - text=plot_note, - xanchor="center", - yanchor="bottom", - xref="paper", - yref="paper", - y=-0.35, - ) + groups = [] + for df in data: + column_name = df.columns[0] + fig = px.line( + df, + x=df["grid_values"], + y=df[column_name], + labels={"grid_values": "Feature value"}, + ) + fig.update_layout(yaxis_title="Partial Dependence") + groups.append( + ArtifactGroup( + title=column_name, artifacts=[PlotlyArtifact(payload=fig)] + ) + ) - return [plotly.io.to_json(fig)] + return [GroupedArtifacts(groups=groups)] - def plot(self, explanation: dict) -> List[dict]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Method to create the explanation plot. Parameters ---------- - explanation: dict - dictionary with the explanation generated by the explainer. - - Returns: - List[dict] - list of JSONs containing the information of the explanation plot - to be rendered. + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per feature and class + curve. """ # Lazy import import pandas as pd diff --git a/DashAI/back/explainability/explainers/permutation_feature_importance.py b/DashAI/back/explainability/explainers/permutation_feature_importance.py index 15d0bfaba..2bd6dc556 100644 --- a/DashAI/back/explainability/explainers/permutation_feature_importance.py +++ b/DashAI/back/explainability/explainers/permutation_feature_importance.py @@ -1,5 +1,10 @@ from typing import Dict, List, Union +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, @@ -437,9 +442,13 @@ def explain(self, dataset): from sklearn.metrics import make_scorer from sklearn.preprocessing import LabelEncoder + from DashAI.back.explainability.model_input import prepare_model_input + x, y = dataset - x_test = x["test"] + # permutation_importance permutes the frame and calls the model with + # it, bypassing the model preparation. + x_test = prepare_model_input(self.model, x["test"]) y_test = y["test"] X_df = x_test.to_pandas() @@ -516,77 +525,49 @@ def patched_metric(y_true, y_pred_probas): "importances_std": np.round(pfi["importances_std"], 3).tolist(), } - def _create_plot(self, data, n_features: int): - """Build a Plotly horizontal bar chart of feature importances. + def _create_plot(self, data) -> List[GroupedArtifacts]: + """Build one selector over feature counts. + + Each count (from all features down to one) is a selectable group + holding the horizontal bar chart of the top ``count`` most important + features, so the frontend lists the counts in a selector instead of a + dropdown embedded in a single figure. Parameters ---------- data : pandas.DataFrame DataFrame with columns ``"features"``, ``"importances_mean"``, and ``"importances_std"``, sorted ascending by importance. - n_features : int - Number of top features (last rows of ``data``) to display in the - default view. A dropdown menu lets users cycle through all counts. Returns ------- - list of str - A single-element list containing the Plotly figure serialised to - JSON via ``plotly.io.to_json``. + List[GroupedArtifacts] + A single grouped artifact with one group (a bar chart) per feature + count, most features first. """ # Lazy imports - import plotly import plotly.express as px - fig = px.bar( - data.iloc[-n_features:], - x=data.iloc[-n_features:]["importances_mean"], - y=data.iloc[-n_features:]["features"], - error_x=data.iloc[-n_features:]["importances_std"], - ) - - fig.update_layout( - xaxis_title="Importance", - yaxis_title=None, - annotations=[ - { - "text": "", - "showarrow": False, - "x": 0, - "y": 1.15, - "xanchor": "left", - "xref": "paper", - "yref": "paper", - "yanchor": "top", - } - ], - updatemenus=[ - { - "x": 0, - "xanchor": "left", - "y": 1.2, - "yanchor": "top", - "buttons": [ - { - "label": f"N° features: {len(data.iloc[-c:,])}", - "method": "restyle", - "args": [ - { - "x": [data.iloc[-c:]["importances_mean"]], - "y": [data.iloc[-c:]["features"]], - "error_x": [data.iloc[-c:]["importances_std"]], - }, - ], - } - for c in range(len(data)) - ], - } - ], - ) + groups = [] + for count in range(len(data), 0, -1): + subset = data.iloc[-count:] + fig = px.bar( + subset, + x=subset["importances_mean"], + y=subset["features"], + error_x=subset["importances_std"], + ) + fig.update_layout(xaxis_title="Importance", yaxis_title=None) + groups.append( + ArtifactGroup( + title=f"Top {count} features", + artifacts=[PlotlyArtifact(payload=fig)], + ) + ) - return [plotly.io.to_json(fig)] + return [GroupedArtifacts(groups=groups)] - def plot(self, explanation: dict) -> List[dict]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Create a Plotly bar chart from a feature importance explanation dict. Parameters @@ -597,18 +578,14 @@ def plot(self, explanation: dict) -> List[dict]: Returns ------- - list of str - A single-element list containing the Plotly figure serialised to - JSON (passed through :meth:`_create_plot`). + List[GroupedArtifacts] + A single selector over the feature counts (built by + :meth:`_create_plot`). """ - n_features = 10 # Lazy import import pandas as pd data = pd.DataFrame.from_dict(explanation) data = data.sort_values(by=["importances_mean"], ascending=True) - if n_features > len(data): - n_features = len(data) - - return self._create_plot(data, n_features) + return self._create_plot(data) diff --git a/DashAI/back/explainability/explainers/regression_kernel_shap.py b/DashAI/back/explainability/explainers/regression_kernel_shap.py new file mode 100644 index 000000000..63661beaf --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_kernel_shap.py @@ -0,0 +1,348 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + bool_field, + float_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionKernelShapSchema(BaseSchema): + """Schema for the regression Kernel SHAP explainer hyperparameters. + + Configures the background sampling used to fit the SHAP explainer. + """ + + fit_parameter_sample_background_data: schema_field( + bool_field(), + placeholder=True, + description=MultilingualString( + en=( + "'true' if background data must be sampled; otherwise the " + "entire training set is used. Smaller datasets speed up the " + "algorithm runtime." + ), + es=( + "'true' si se deben muestrear los datos de fondo; de lo " + "contrario se usa el conjunto de entrenamiento completo. " + "Conjuntos más pequeños reducen el tiempo de ejecución." + ), + pt=( + "'true' se os dados de fundo devem ser amostrados; caso " + "contrário, usa-se o conjunto de treinamento completo. " + "Conjuntos menores reduzem o tempo de execução." + ), + zh=( + "如果需要对背景数据进行采样则为'true';" + "否则使用整个训练集。较小的数据集可加速算法运行。" + ), + de=( + "'true', wenn Hintergrunddaten gesamplet werden müssen; sonst " + "wird der gesamte Trainingssatz verwendet. Kleinere " + "Datensätze beschleunigen die Laufzeit." + ), + ), + alias=MultilingualString( + en="Sample background data", + es="Muestrear datos de fondo", + pt="Amostrar dados de fundo", + zh="采样背景数据", + de="Hintergrunddaten samplen", + ), + ) # type: ignore + + fit_parameter_background_fraction: schema_field( + float_field(ge=0, le=1), + placeholder=0.2, + description=MultilingualString( + en=( + "If 'Sample background data' is selected, fraction of " + "background samples to draw from the training set." + ), + es=( + "Si se selecciona 'Muestrear datos de fondo', proporción de " + "muestras de fondo a extraer del conjunto de entrenamiento." + ), + pt=( + "Se 'Amostrar dados de fundo' estiver selecionado, fração de " + "amostras de fundo a extrair do conjunto de treinamento." + ), + zh="如果选择了'采样背景数据',则为从训练集中抽取的背景样本比例。", + de=( + "Wenn 'Hintergrunddaten samplen' ausgewählt ist, Anteil der " + "Hintergrundproben aus dem Trainingssatz." + ), + ), + alias=MultilingualString( + en="Background fraction", + es="Fracción de fondo", + pt="Fração de fundo", + zh="背景比例", + de="Hintergrundfraktion", + ), + ) # type: ignore + + +class RegressionKernelShap(BaseLocalExplainer): + """Model agnostic local explainer for regression via Kernel SHAP. + + For each instance, estimates how much each feature value pushed the + model's numeric prediction above or below the expected (baseline) output, + using the Kernel SHAP weighted linear model over sampled feature + coalitions. The model is treated as a black box: only ``predict`` is + queried. + + References + ---------- + - [1] Lundberg, S.M. & Lee, S.I. (2017). "A Unified Approach to + Interpreting Model Predictions." NeurIPS 30. + https://arxiv.org/abs/1705.07874 + - [2] https://shap.readthedocs.io/en/latest/generated/shap.KernelExplainer.html + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Kernel SHAP (regression)", + es="Kernel SHAP (regresión)", + pt="Kernel SHAP (regressão)", + zh="Kernel SHAP(回归)", + de="Kernel SHAP (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Attributes a regression model's numeric prediction to each " + "feature value using SHAP values." + ), + es=( + "Atribuye la predicción numérica de un modelo de regresión a cada " + "valor de característica usando valores SHAP." + ), + pt=( + "Atribui a previsão numérica de um modelo de regressão a cada " + "valor de característica usando valores SHAP." + ), + zh="使用SHAP值将回归模型的数值预测归因于每个特征值。", + de=( + "Ordnet die numerische Vorhersage eines Regressionsmodells jedem " + "Merkmalswert mittels SHAP-Werten zu." + ), + ) + COLOR = "#00838F" + SCHEMA = RegressionKernelShapSchema + + def __init__(self, model: BaseModel) -> None: + """Initialize a new instance of a RegressionKernelShap explainer. + + Parameters + ---------- + model : BaseModel + Regression model to be explained. + """ + super().__init__(model) + + def fit( + self, + background_dataset, + sample_background_data=False, + background_fraction=None, + **kwargs, + ): + """Fit the Kernel SHAP explainer on background data. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits; the train split is used + as SHAP background data. + sample_background_data : bool + True if the background data must be sampled. + background_fraction : float + Fraction of the training samples used as background data when + ``sample_background_data`` is True. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + RegressionKernelShap + The fitted explainer instance (``self``). + """ + import shap + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = background_dataset + # SHAP calls the model with perturbed frames, which skip the model + # preparation, so the background must be in the model feature space. + x_train = prepare_model_input(self.model, x["train"]) + y_train = y["train"] + + background_data = x_train.to_pandas() + feature_names = list(x_train.column_names) + + if bool(sample_background_data) and background_fraction: + n_samples = max(1, int(background_fraction * len(background_data))) + background_data = shap.sample(background_data, n_samples) + + self.explainer = shap.KernelExplainer( + model=self.model.predict, + data=background_data, + feature_names=feature_names, + ) + + self.metadata = { + "feature_names": feature_names, + "output_column": y_train.column_names[0], + } + + return self + + def explain_instance(self, instances): + """Compute SHAP values for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction, the + baseline value and the per-feature SHAP values. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + from DashAI.back.explainability.model_input import prepare_model_input + + dataset = to_dashai_dataset(instances) + X = prepare_model_input(self.model, dataset).to_pandas() + + predictions = np.asarray(self.model.predict(dataset)).ravel() + + shap_values = np.asarray(self.explainer.shap_values(X=X)) + # Single-output models may yield (n, n_features) or (n, n_features, 1). + if shap_values.ndim == 3: + shap_values = shap_values[..., 0] + + base_value = np.asarray(self.explainer.expected_value).ravel()[0] + + explanation = { + "metadata": self.metadata, + "base_value": float(np.round(base_value, 3)), + } + for i, (instance, prediction, contributions) in enumerate( + zip(X.to_numpy(), predictions, shap_values, strict=True) + ): + explanation[i] = { + "instance_values": instance.tolist(), + "model_prediction": float(np.round(prediction, 3)), + "shap_values": np.round(contributions, 3).tolist(), + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a SHAP bar plot plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's plotly plot and text summary. + """ + import numpy as np + import pandas as pd + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + base_value = exp.pop("base_value") + feature_names = metadata["feature_names"] + output_column = metadata["output_column"] + max_features = 8 + + groups = [] + for i in exp: + instance = exp[i] + prediction = instance["model_prediction"] + + data = pd.DataFrame( + { + "features": feature_names, + "values": instance["instance_values"], + "shap_values": instance["shap_values"], + } + ) + data["shap_abs"] = data["shap_values"].abs() + data = data.sort_values(by="shap_abs", ascending=True) + if len(data) > max_features: + data = data.iloc[-max_features:, :] + data["label"] = data["features"] + "=" + data["values"].map(str) + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["shap_values"] + ] + fig = go.Figure( + go.Bar( + x=data["shap_values"], + y=data["label"], + orientation="h", + marker={"color": colors}, + text=data["shap_values"], + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"{output_column}: prediction f(x)={prediction}, " + f"baseline E[f(x)]={base_value}" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "SHAP value (impact on prediction)"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + plot = PlotlyArtifact(payload=fig) + + top = data.iloc[::-1].head(3) + top_features = ", ".join( + f"{feature}={value} ({shap:+})" + for feature, value, shap in zip( + top["features"].tolist(), + top["values"].tolist(), + top["shap_values"].tolist(), + strict=True, + ) + ) + delta = float(np.round(prediction - base_value, 3)) + summary = ( + f"The model predicted {output_column}={prediction}, " + f"{delta:+} from the baseline {base_value}. " + f"Main contributions: {top_features}." + ) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/regression_partial_dependence.py b/DashAI/back/explainability/explainers/regression_partial_dependence.py new file mode 100644 index 000000000..5c6706523 --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_partial_dependence.py @@ -0,0 +1,255 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.global_explainer import BaseGlobalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionPartialDependenceSchema(BaseSchema): + """Schema for the regression Partial Dependence explainer. + + Configures the grid resolution and the percentile range of each + feature's grid. + """ + + grid_resolution: schema_field( + int_field(ge=5, le=200), + placeholder=50, + description=MultilingualString( + en="Number of equally spaced grid points per feature.", + es="Número de puntos de la grilla equiespaciados por característica.", + pt="Número de pontos de grade igualmente espaçados por característica.", + zh="每个特征等距网格点的数量。", + de="Anzahl gleichmäßig verteilter Gitterpunkte pro Merkmal.", + ), + alias=MultilingualString( + en="Grid resolution", + es="Resolución de la grilla", + pt="Resolução da grade", + zh="网格分辨率", + de="Gitterauflösung", + ), + ) # type: ignore + + lower_percentile: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.05, + description=MultilingualString( + en="Lower percentile of the feature values used as grid start.", + es="Percentil inferior de los valores usados como inicio de la grilla.", + pt="Percentil inferior dos valores usados como início da grade.", + zh="用作网格起点的特征值下分位数。", + de="Unteres Perzentil der Merkmalswerte als Gitterstart.", + ), + alias=MultilingualString( + en="Lower percentile", + es="Percentil inferior", + pt="Percentil inferior", + zh="下分位数", + de="Unteres Perzentil", + ), + ) # type: ignore + + upper_percentile: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.95, + description=MultilingualString( + en="Upper percentile of the feature values used as grid end.", + es="Percentil superior de los valores usados como fin de la grilla.", + pt="Percentil superior dos valores usados como fim da grade.", + zh="用作网格终点的特征值上分位数。", + de="Oberes Perzentil der Merkmalswerte als Gitterende.", + ), + alias=MultilingualString( + en="Upper percentile", + es="Percentil superior", + pt="Percentil superior", + zh="上分位数", + de="Oberes Perzentil", + ), + ) # type: ignore + + +class RegressionPartialDependence(BaseGlobalExplainer): + """Partial dependence curves for regression models. + + For each numeric feature, sweeps a grid of values, replaces the feature + with each grid value across the test set and averages the model's + predictions, showing the marginal effect of the feature on the predicted + value. Model agnostic (only ``predict`` is queried); assumes features are + not strongly correlated. + + References + ---------- + - [1] Friedman, J.H. (2001). "Greedy Function Approximation: A Gradient + Boosting Machine." Annals of Statistics 29(5). + - [2] https://scikit-learn.org/stable/modules/partial_dependence.html + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Partial Dependence (regression)", + es="Dependencia Parcial (regresión)", + pt="Dependência Parcial (regressão)", + zh="部分依赖(回归)", + de="Partielle Abhängigkeit (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Shows how the model's predicted value changes on average as each " + "feature sweeps through its range." + ), + es=( + "Muestra cómo cambia en promedio el valor predicho por el modelo " + "a medida que cada característica recorre su rango." + ), + pt=( + "Mostra como o valor previsto pelo modelo muda em média à medida " + "que cada característica percorre seu intervalo." + ), + zh="展示随着每个特征遍历其取值范围,模型预测值的平均变化。", + de=( + "Zeigt, wie sich der vorhergesagte Wert des Modells im Mittel " + "ändert, wenn jedes Merkmal seinen Wertebereich durchläuft." + ), + ) + COLOR = "#5D4037" + SCHEMA = RegressionPartialDependenceSchema + + def __init__( + self, + model: BaseModel, + grid_resolution: int = 50, + lower_percentile: float = 0.05, + upper_percentile: float = 0.95, + ): + """Initialise the regression Partial Dependence explainer. + + Parameters + ---------- + model : BaseModel + The trained DashAI regression model to be explained. + grid_resolution : int + Number of grid points per feature. + lower_percentile : float + Lower percentile of the feature values used as grid start. + upper_percentile : float + Upper percentile of the feature values used as grid end. + """ + super().__init__(model) + assert lower_percentile < upper_percentile, ( + "lower_percentile must be smaller than upper_percentile" + ) + self.grid_resolution = grid_resolution + self.lower_percentile = lower_percentile + self.upper_percentile = upper_percentile + + def explain(self, dataset): + """Compute partial dependence curves on the test split. + + Parameters + ---------- + dataset : Tuple[DatasetDict, DatasetDict] + A ``(x, y)`` pair where each element has at least a ``"test"`` + split. + + Returns + ------- + dict + Mapping from feature name to ``{"grid_values", "average"}``, + plus a ``"metadata"`` entry with the output column name. + """ + import numpy as np + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = dataset + # The grid frames are passed straight to the model, bypassing the + # model preparation. + x_test = prepare_model_input(self.model, x["test"]).to_pandas() + + # Cap rows to bound the number of model evaluations. + max_rows = 200 + if len(x_test) > max_rows: + x_test = x_test.iloc[:max_rows] + + output_column = y["test"].column_names[0] + explanation = {"metadata": {"output_column": output_column}} + + for column in x_test.columns: + if not np.issubdtype(x_test[column].dtype, np.number): + continue + + values = x_test[column].to_numpy(dtype=float) + start = np.quantile(values, self.lower_percentile) + stop = np.quantile(values, self.upper_percentile) + grid = np.linspace(start, stop, self.grid_resolution) + + averages = [] + frame = x_test.copy() + for grid_value in grid: + frame[column] = grid_value + predictions = np.asarray(self.model.predict(frame)).ravel() + averages.append(float(np.round(np.mean(predictions), 4))) + + explanation[column] = { + "grid_values": np.round(grid, 4).tolist(), + "average": averages, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Create a grouped artifact with one line plot group per feature. + + Parameters + ---------- + explanation : dict + Output of :meth:`explain`. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact whose groups are one plotly curve per + numeric feature. + """ + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + output_column = metadata["output_column"] + + groups = [] + for feature, curve in exp.items(): + fig = go.Figure( + go.Scatter( + x=curve["grid_values"], + y=curve["average"], + mode="lines", + ) + ) + fig.update_layout( + title={ + "text": f"Partial dependence of {output_column} on {feature}", + "font": {"size": 14}, + }, + xaxis={"title_text": feature}, + yaxis={"title_text": f"Average predicted {output_column}"}, + margin={"l": 60, "r": 30, "t": 50, "b": 50}, + ) + groups.append( + ArtifactGroup(title=feature, artifacts=[PlotlyArtifact(payload=fig)]) + ) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py b/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py new file mode 100644 index 000000000..d341a99f0 --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py @@ -0,0 +1,316 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.global_explainer import BaseGlobalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionPermutationFeatureImportanceSchema(BaseSchema): + """Schema for the regression Permutation Feature Importance explainer. + + Configures the regression scoring metric, the number of permutation + repeats per feature and the random seed. + """ + + scoring: schema_field( + enum_field(enum=["r2", "neg_mean_squared_error", "neg_mean_absolute_error"]), + placeholder="r2", + description=MultilingualString( + en=( + "Regression metric used to evaluate how the model's " + "performance changes when a particular feature is shuffled." + ), + es=( + "Métrica de regresión utilizada para evaluar cómo cambia el " + "rendimiento del modelo cuando se baraja una característica." + ), + pt=( + "Métrica de regressão usada para avaliar como o desempenho do " + "modelo muda quando uma característica é embaralhada." + ), + zh="用于评估特定特征被打乱时模型性能变化的回归指标。", + de=( + "Regressionsmetrik zur Bewertung, wie sich die Modellleistung " + "ändert, wenn ein bestimmtes Merkmal permutiert wird." + ), + ), + alias=MultilingualString( + en="Scoring metric", + es="Métrica de evaluación", + pt="Métrica de avaliação", + zh="评分指标", + de="Bewertungsmetrik", + ), + ) # type: ignore + + n_repeats: schema_field( + int_field(ge=1), + placeholder=10, + description=MultilingualString( + en="Number of times to permute a feature.", + es="Número de veces que se permuta una característica.", + pt="Número de vezes que uma característica é permutada.", + zh="对特征进行排列的次数。", + de="Anzahl der Permutationen eines Merkmals.", + ), + alias=MultilingualString( + en="Number of repeats", + es="Número de repeticiones", + pt="Número de repetições", + zh="重复次数", + de="Anzahl der Wiederholungen", + ), + ) # type: ignore + + random_state: schema_field( + int_field(), + placeholder=0, + description=MultilingualString( + en=( + "Seed for the random number generator to control permutations " + "of each feature." + ), + es=( + "Semilla del generador aleatorio para controlar las " + "permutaciones de cada característica." + ), + pt=( + "Semente do gerador de números aleatórios para controlar as " + "permutações de cada característica." + ), + zh="用于控制每个特征排列的随机数生成器种子。", + de=( + "Startwert für den Zufallszahlengenerator zur Steuerung der " + "Permutationen jedes Merkmals." + ), + ), + alias=MultilingualString( + en="Random state", + es="Semilla aleatoria", + pt="Estado aleatório", + zh="随机状态", + de="Zufallszustand", + ), + ) # type: ignore + + max_samples_fraction: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=1.0, + description=MultilingualString( + en=( + "Fraction of samples to draw from the test set to calculate " + "feature importance at each repetition." + ), + es=( + "Fracción de muestras a extraer del conjunto de prueba para " + "calcular la importancia en cada repetición." + ), + pt=( + "Fração de amostras a extrair do conjunto de teste para " + "calcular a importância a cada repetição." + ), + zh="每次重复时从测试集中抽取的样本比例。", + de=( + "Anteil der aus dem Testdatensatz gezogenen Stichproben zur " + "Berechnung der Merkmalswichtigkeit." + ), + ), + alias=MultilingualString( + en="Max samples fraction", + es="Fracción máxima de muestras", + pt="Fração máxima de amostras", + zh="最大样本比例", + de="Maximaler Stichprobenanteil", + ), + ) # type: ignore + + +class RegressionPermutationFeatureImportance(BaseGlobalExplainer): + """Global permutation feature importance for regression models. + + Measures the importance of each feature by randomly shuffling its values + across the test set and recording the resulting decrease in a regression + scoring metric (R2, negative MSE or negative MAE). Repeating the + permutation ``n_repeats`` times yields a mean importance and standard + deviation per feature. The method is model agnostic and computed on held + out data. + + References + ---------- + - [1] Breiman, L. (2001). "Random Forests." Machine Learning, 45(1), 5-32. + - [2] Fisher, A. et al. (2019). "All Models are Wrong, but Many are + Useful." JMLR, 20(177), 1-81. https://arxiv.org/abs/1801.01489 + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Permutation Feature Importance (regression)", + es="Importancia por Permutación (regresión)", + pt="Importância por Permutação (regressão)", + zh="排列特征重要性(回归)", + de="Permutations-Merkmalswichtigkeit (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Assesses feature importance for regression models by measuring " + "the drop in a regression metric when a feature's values are " + "randomly shuffled." + ), + es=( + "Evalúa la importancia de las características en modelos de " + "regresión midiendo la caída de una métrica de regresión cuando " + "los valores de una característica se barajan aleatoriamente." + ), + pt=( + "Avalia a importância das características em modelos de regressão " + "medindo a queda de uma métrica de regressão quando os valores de " + "uma característica são embaralhados aleatoriamente." + ), + zh="通过测量特征值被随机打乱时回归指标的下降来评估回归模型的特征重要性。", + de=( + "Bewertet die Merkmalswichtigkeit von Regressionsmodellen durch " + "Messung des Abfalls einer Regressionsmetrik, wenn die Werte " + "eines Merkmals zufällig permutiert werden." + ), + ) + COLOR = "#3F51B5" + SCHEMA = RegressionPermutationFeatureImportanceSchema + + def __init__( + self, + model: BaseModel, + scoring: str = "r2", + n_repeats: int = 10, + random_state: int = None, + max_samples_fraction: float = 1.0, + ): + """Initialise the regression permutation feature importance explainer. + + Parameters + ---------- + model : BaseModel + The trained DashAI regression model to be explained. + scoring : str + Regression metric: 'r2', 'neg_mean_squared_error' or + 'neg_mean_absolute_error'. + n_repeats : int + Number of times each feature is permuted. + random_state : int or None + Seed for the random number generator controlling permutations. + max_samples_fraction : float + Fraction of the test set sampled for the calculation. + """ + super().__init__(model) + + from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + + metrics = { + "r2": r2_score, + "neg_mean_squared_error": lambda y_true, y_pred: ( + -mean_squared_error(y_true, y_pred) + ), + "neg_mean_absolute_error": lambda y_true, y_pred: ( + -mean_absolute_error(y_true, y_pred) + ), + } + + self.scoring_name = scoring + self.scoring = metrics[scoring] + self.n_repeats = n_repeats + self.random_state = random_state + self.max_samples_fraction = max_samples_fraction + + def explain(self, dataset): + """Compute permutation feature importance on the test split. + + Parameters + ---------- + dataset : Tuple[DatasetDict, DatasetDict] + A ``(x, y)`` pair where each element has at least a ``"test"`` + split. + + Returns + ------- + dict + Dictionary with keys ``"features"``, ``"importances_mean"`` and + ``"importances_std"``. + """ + import numpy as np + + from DashAI.back.explainability.model_input import prepare_model_input + + x, y = dataset + # The permuted frames are passed straight to the model, bypassing the + # model preparation. + x_test = prepare_model_input(self.model, x["test"]).to_pandas() + y_test = y["test"].to_pandas().to_numpy().ravel() + + rng = np.random.RandomState(self.random_state) + n_samples = max(1, int(len(x_test) * self.max_samples_fraction)) + sample_indexes = rng.choice(len(x_test), size=n_samples, replace=False) + x_sample = x_test.iloc[sample_indexes].reset_index(drop=True) + y_sample = y_test[sample_indexes] + + baseline_score = self.scoring( + y_sample, np.asarray(self.model.predict(x_sample)).ravel() + ) + + results = {"features": [], "importances_mean": [], "importances_std": []} + for column in x_sample.columns: + importances = [] + for _ in range(self.n_repeats): + x_permuted = x_sample.copy() + x_permuted[column] = x_sample[column].to_numpy()[ + rng.permutation(n_samples) + ] + permuted_score = self.scoring( + y_sample, np.asarray(self.model.predict(x_permuted)).ravel() + ) + importances.append(baseline_score - permuted_score) + + results["features"].append(column) + results["importances_mean"].append(float(np.round(np.mean(importances), 3))) + results["importances_std"].append(float(np.round(np.std(importances), 3))) + + return results + + def plot(self, explanation: dict) -> List[Artifact]: + """Create a bar chart of feature importances. + + Parameters + ---------- + explanation : dict + Output of :meth:`explain`. + + Returns + ------- + List[Artifact] + A list with a single plotly artifact holding the importance bar + chart. + """ + import pandas as pd + import plotly.express as px + + data = pd.DataFrame.from_dict(explanation) + data = data.sort_values(by=["importances_mean"], ascending=True) + + fig = px.bar( + data, + x=data["importances_mean"], + y=data["features"], + error_x=data["importances_std"], + ) + fig.update_layout( + xaxis_title=f"Importance ({self.scoring_name})", + yaxis_title=None, + ) + + return [PlotlyArtifact(payload=fig, title="Permutation Feature Importance")] diff --git a/DashAI/back/explainability/explainers/token_ablation.py b/DashAI/back/explainability/explainers/token_ablation.py new file mode 100644 index 000000000..401daf1c8 --- /dev/null +++ b/DashAI/back/explainability/explainers/token_ablation.py @@ -0,0 +1,364 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class TokenAblationSchema(BaseSchema): + """Schema for the Token Ablation explainer hyperparameters. + + Configures how many tokens are evaluated per instance and how ablated + tokens are replaced. + """ + + max_tokens: schema_field( + int_field(ge=1, le=256), + placeholder=50, + description=MultilingualString( + en=( + "Maximum number of tokens (whitespace-separated words) " + "evaluated per instance. Texts longer than this are truncated " + "for the analysis to bound the number of model calls." + ), + es=( + "Número máximo de tokens (palabras separadas por espacios) " + "evaluados por instancia. Los textos más largos se truncan " + "para el análisis para limitar las llamadas al modelo." + ), + pt=( + "Número máximo de tokens (palavras separadas por espaços) " + "avaliados por instância. Textos mais longos são truncados " + "para a análise para limitar as chamadas ao modelo." + ), + zh="每个实例评估的最大token数(按空格分词)。超长文本将被截断以限制模型调用次数。", + de=( + "Maximale Anzahl der pro Instanz ausgewerteten Tokens (durch " + "Leerzeichen getrennte Wörter). Längere Texte werden für die " + "Analyse gekürzt, um die Modellaufrufe zu begrenzen." + ), + ), + alias=MultilingualString( + en="Max tokens", + es="Máximo de tokens", + pt="Máximo de tokens", + zh="最大token数", + de="Maximale Tokenanzahl", + ), + ) # type: ignore + + replacement: schema_field( + enum_field(enum=["remove", "unk"]), + placeholder="remove", + description=MultilingualString( + en=( + "How an ablated token is handled: 'remove' deletes it from " + "the text, 'unk' replaces it with the [UNK] placeholder." + ), + es=( + "Cómo se trata un token eliminado: 'remove' lo borra del " + "texto, 'unk' lo reemplaza por el marcador [UNK]." + ), + pt=( + "Como um token removido é tratado: 'remove' o exclui do " + "texto, 'unk' o substitui pelo marcador [UNK]." + ), + zh="被消融token的处理方式:'remove'从文本中删除,'unk'替换为[UNK]占位符。", + de=( + "Behandlung eines entfernten Tokens: 'remove' löscht es aus " + "dem Text, 'unk' ersetzt es durch den Platzhalter [UNK]." + ), + ), + alias=MultilingualString( + en="Replacement strategy", + es="Estrategia de reemplazo", + pt="Estratégia de substituição", + zh="替换策略", + de="Ersetzungsstrategie", + ), + ) # type: ignore + + +class TokenAblation(BaseLocalExplainer): + """Occlusion-based local explainer for text classification. + + For each instance, ablates one token at a time (removing it or replacing + it with an [UNK] placeholder) and measures how much the predicted class + probability drops. Tokens whose removal causes a large drop are the ones + the model relied on for its prediction. The method is model agnostic: it + only queries ``predict``, so it works with any text classifier. + + References + ---------- + - [1] Zeiler, M.D. & Fergus, R. (2014). "Visualizing and Understanding + Convolutional Networks." ECCV 2014. https://arxiv.org/abs/1311.2901 + - [2] Li, J. et al. (2016). "Understanding Neural Networks through + Representation Erasure." https://arxiv.org/abs/1612.08220 + """ + + COMPATIBLE_COMPONENTS = ["TextClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Token Ablation", + es="Ablación de tokens", + pt="Ablação de tokens", + zh="Token消融", + de="Token-Ablation", + ) + DESCRIPTION = MultilingualString( + en=( + "Measures each word's importance by removing it from the text " + "and recording the drop in the predicted class probability." + ), + es=( + "Mide la importancia de cada palabra eliminándola del texto y " + "registrando la caída en la probabilidad de la clase predicha." + ), + pt=( + "Mede a importância de cada palavra removendo-a do texto e " + "registrando a queda na probabilidade da classe prevista." + ), + zh="通过从文本中删除每个词并记录预测类别概率的下降来衡量词的重要性。", + de=( + "Misst die Wichtigkeit jedes Wortes, indem es aus dem Text " + "entfernt und der Rückgang der vorhergesagten " + "Klassenwahrscheinlichkeit erfasst wird." + ), + ) + COLOR = "#E65100" + SCHEMA = TokenAblationSchema + + def __init__( + self, + model: BaseModel, + max_tokens: int = 50, + replacement: str = "remove", + ) -> None: + """Initialize a new instance of a TokenAblation explainer. + + Parameters + ---------- + model : BaseModel + Text classification model to be explained. + max_tokens : int + Maximum number of tokens evaluated per instance. + replacement : str + 'remove' to delete the token, 'unk' to replace it with [UNK]. + """ + super().__init__(model) + self.max_tokens = max_tokens + self.replacement = replacement + + def fit(self, background_dataset, **kwargs): + """Store class names from the training targets. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + TokenAblation + The fitted explainer instance (``self``). + """ + _, y = background_dataset + y_train = y["train"] + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = {"target_names": list(target_names)} + + return self + + def _ablate(self, tokens, index): + """Build the text variant with the token at ``index`` ablated. + + Parameters + ---------- + tokens : List[str] + Whitespace tokens of the original text. + index : int + Position of the token to ablate. + + Returns + ------- + str + The perturbed text. + """ + if self.replacement == "unk": + variant = tokens.copy() + variant[index] = "[UNK]" + return " ".join(variant) + return " ".join(tokens[:index] + tokens[index + 1 :]) + + def explain_instance(self, instances): + """Compute token importances for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained; must contain a single text column. + + Returns + ------- + dict + Dictionary with, for each instance, the tokens, their importance + (probability drop when ablated) and the model prediction. + """ + import numpy as np + import pandas as pd + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + # The job may hand over an already-prepared dataset (e.g. tokenized by + # a transformer model, adding input_ids/attention_mask columns). + # Rebuild a clean single-text-column dataset so that model.predict can + # run its own preparation from raw text. + tokenizer_columns = {"input_ids", "attention_mask", "token_type_ids", "label"} + text_columns = [c for c in X.columns if c not in tokenizer_columns] + if not text_columns: + raise ValueError(f"No text column found among columns: {list(X.columns)}") + text_column = text_columns[0] + texts = X[text_column].astype(str).tolist() + + base_dataset = to_dashai_dataset(pd.DataFrame({text_column: texts})) + base_predictions = np.asarray(self.model.predict(base_dataset)) + + explanation = {"metadata": {**self.metadata, "text_column": text_column}} + for i, text in enumerate(texts): + tokens = str(text).split()[: self.max_tokens] + predicted_class = int(np.argmax(base_predictions[i])) + base_prob = float(base_predictions[i][predicted_class]) + + importances = [] + if tokens: + variants = [self._ablate(tokens, index) for index in range(len(tokens))] + variants_dataset = to_dashai_dataset( + pd.DataFrame({text_column: variants}) + ) + variant_predictions = np.asarray(self.model.predict(variants_dataset)) + importances = [ + float( + np.round(base_prob - variant_predictions[j][predicted_class], 4) + ) + for j in range(len(tokens)) + ] + + explanation[i] = { + "text": str(text), + "tokens": tokens, + "token_importances": importances, + "model_prediction": base_predictions[i].tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a token importance bar plot plus a summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's token plot and text summary. + """ + import numpy as np + import pandas as pd + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + max_tokens_plotted = 15 + + groups = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + data = pd.DataFrame( + { + "tokens": [ + f"{token} ({position})" + for position, token in enumerate(instance["tokens"]) + ], + "importances": instance["token_importances"], + } + ) + data["importance_abs"] = data["importances"].abs() + data = data.sort_values(by="importance_abs", ascending=True) + if len(data) > max_tokens_plotted: + data = data.iloc[-max_tokens_plotted:, :] + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["importances"] + ] + fig = go.Figure( + go.Bar( + x=data["importances"], + y=data["tokens"], + orientation="h", + marker={"color": colors}, + text=data["importances"], + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"Token importance for prediction {predicted_name} " + f"(p={predicted_prob})" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Probability drop when token is ablated"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + plot = PlotlyArtifact(payload=fig) + + top = data.iloc[::-1].head(3) + top_tokens = ", ".join( + f"'{token}' ({importance:+})" + for token, importance in zip( + top["tokens"].tolist(), top["importances"].tolist(), strict=True + ) + ) + summary = ( + f"The model predicted {predicted_name} (p={predicted_prob}). " + f"Most influential tokens: {top_tokens}." + ) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) + + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/global_explainer.py b/DashAI/back/explainability/global_explainer.py index 90dddf00d..601d3dbb3 100644 --- a/DashAI/back/explainability/global_explainer.py +++ b/DashAI/back/explainability/global_explainer.py @@ -1,7 +1,8 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Final, List, Tuple +from typing import TYPE_CHECKING, Final, List, Tuple, Union from DashAI.back.config_object import ConfigObject +from DashAI.back.core.artifacts import Artifact, GroupedArtifacts from DashAI.back.models.base_model import BaseModel if TYPE_CHECKING: @@ -18,8 +19,8 @@ class BaseGlobalExplainer(ConfigObject, ABC): partial dependence curves, or aggregate attribution scores. All concrete global explainers must implement :meth:`explain` (compute the - explanation from a dataset) and :meth:`plot` (serialise the explanation as - Plotly figures for the frontend). + explanation from a dataset) and :meth:`plot` (turn the explanation into + renderable artifacts for the frontend). """ TYPE: Final[str] = "GlobalExplainer" @@ -64,12 +65,12 @@ def explain(self, dataset: Tuple["DatasetDict", "DatasetDict"]) -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> List[dict]: - """Generate serialised plots from a previously computed explanation. + def plot(self, explanation: dict) -> List[Union[Artifact, GroupedArtifacts]]: + """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary - returned by :meth:`explain` into one or more serialised plot objects - that can be rendered on the frontend. + returned by :meth:`explain` into one or more typed artifacts that + can be rendered on the frontend. Parameters ---------- @@ -78,10 +79,12 @@ def plot(self, explanation: dict) -> List[dict]: Returns ------- - List[dict] - A list of serialised plot objects. Each element is a JSON string - (produced by ``plotly.io.to_json``) representing a single Plotly - figure. + List[Union[Artifact, GroupedArtifacts]] + A list of artifacts (:class:`PlotlyArtifact`, + :class:`TableArtifact`, :class:`TextArtifact` or + :class:`ImageArtifact`) and/or :class:`GroupedArtifacts` batches + (e.g. a summary table next to its plot for one curve/count) that + the frontend should render together, describing the explanation. Raises ------ diff --git a/DashAI/back/explainability/local_explainer.py b/DashAI/back/explainability/local_explainer.py index 79409dafe..5eb66d847 100644 --- a/DashAI/back/explainability/local_explainer.py +++ b/DashAI/back/explainability/local_explainer.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Final, List, Tuple from DashAI.back.config_object import ConfigObject +from DashAI.back.core.artifacts import GroupedArtifacts from DashAI.back.models.base_model import BaseModel if TYPE_CHECKING: @@ -19,7 +20,7 @@ class BaseLocalExplainer(ConfigObject, ABC): Concrete implementations must provide :meth:`fit` (prepare background data or internal state), :meth:`explain_instance` (compute per-instance attributions), - and :meth:`plot` (serialise explanations as Plotly figures). + and :meth:`plot` (turn explanations into renderable artifacts). """ TYPE: Final[str] = "LocalExplainer" @@ -87,12 +88,14 @@ def explain_instance(self, instances: "DatasetDict") -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> List[dict]: - """Generate serialised plots from a previously computed explanation. + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary - returned by :meth:`explain_instance` into one or more serialised plot - objects that can be rendered on the frontend. + returned by :meth:`explain_instance` into typed artifacts that can be + rendered on the frontend. Local explainers typically return a single + :class:`GroupedArtifacts` whose groups are one explained instance each + (the frontend renders its selector as the explained rows picker). Parameters ---------- @@ -101,10 +104,9 @@ def plot(self, explanation: dict) -> List[dict]: Returns ------- - List[dict] - A list of serialised plot objects. Each element is a JSON string - (produced by ``plotly.io.to_json``) representing a single Plotly - figure. + List[GroupedArtifacts] + A list containing a single grouped artifact whose groups + are one explained instance each. Raises ------ diff --git a/DashAI/back/explainability/model_input.py b/DashAI/back/explainability/model_input.py new file mode 100644 index 000000000..0387d3801 --- /dev/null +++ b/DashAI/back/explainability/model_input.py @@ -0,0 +1,42 @@ +"""Helper to move an explainer's data into the model's feature space. + +Explainers receive the model input as the task prepared it, exactly as +``predict`` receives it in the prediction job: raw columns, before any model +specific preprocessing. That is what explainers that perturb the input and +query ``model.predict`` need, since ``predict`` applies the model preparation +itself. + +Explainers that instead build feature matrices (``to_pandas``) and hand them +to a third party library (SHAP, DiCE, scikit-learn inspection) must work in +the model's own feature space, because those libraries call the model with +plain frames that bypass the model preparation. Such explainers call +:func:`prepare_model_input` on both the background data and the instances so +that both live in the same space. +""" + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +def prepare_model_input(model: Any, dataset: "DashAIDataset") -> "DashAIDataset": + """Apply the model's own input preprocessing to a dataset. + + Parameters + ---------- + model : Any + The DashAI model being explained. + dataset : DashAIDataset + Input features as the task prepared them. + + Returns + ------- + DashAIDataset + The dataset in the model's feature space, or the dataset unchanged + when the model does not define ``prepare_dataset``. + """ + prepare = getattr(model, "prepare_dataset", None) + if prepare is None: + return dataset + return prepare(dataset, is_fit=False) diff --git a/DashAI/back/exploration/base_explorer.py b/DashAI/back/exploration/base_explorer.py index ed3e9c926..cc27b071b 100644 --- a/DashAI/back/exploration/base_explorer.py +++ b/DashAI/back/exploration/base_explorer.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Dict, Final, List from DashAI.back.config_object import ConfigObject +from DashAI.back.core.artifacts import Artifact from DashAI.back.core.schema_fields import BaseSchema from DashAI.back.dependencies.database.models import Explorer, Notebook from DashAI.back.static.icons import Icon @@ -276,7 +277,7 @@ def save_notebook( @abstractmethod def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load a previously saved exploration result and return it for the frontend. Parameters @@ -289,9 +290,11 @@ def get_results( Returns ------- - Dict[str, Any] - A dict with keys ``"data"`` (serialized result), - ``"type"`` (result type string, e.g. ``"plotly_json"``), and - ``"config"`` (frontend rendering config). + List[Artifact] + A list of artifacts (:class:`PlotlyArtifact`, + :class:`TableArtifact`, :class:`TextArtifact` or + :class:`ImageArtifact`) describing the exploration result. + Legacy explorers returning the old ``{"data", "type", "config"}`` + dict are upgraded by ``normalize_artifacts`` at the API layer. """ raise NotImplementedError diff --git a/DashAI/back/exploration/explorers/box_plot.py b/DashAI/back/exploration/explorers/box_plot.py index 611232e57..602531de5 100644 --- a/DashAI/back/exploration/explorers/box_plot.py +++ b/DashAI/back/exploration/explorers/box_plot.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import bool_field, enum_field, schema_field from DashAI.back.core.utils import MultilingualString from DashAI.back.dependencies.database.models import Explorer, Notebook @@ -246,7 +247,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved box plot for the frontend. Parameters @@ -258,17 +259,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - from plotly.io import read_json + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/corr_matrix.py b/DashAI/back/exploration/explorers/corr_matrix.py index 2e9b00b33..cb115c2a3 100644 --- a/DashAI/back/exploration/explorers/corr_matrix.py +++ b/DashAI/back/exploration/explorers/corr_matrix.py @@ -1,6 +1,12 @@ from enum import Enum -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import ( + Artifact, + PlotlyArtifact, + TableArtifact, + TablePayload, +) from DashAI.back.core.schema_fields import ( bool_field, enum_field, @@ -326,46 +332,49 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved correlation result for the frontend. When ``self.plot`` is ``True``, reads the raw Plotly JSON string from - disk. Otherwise reads the JSON file as a pandas DataFrame and converts - it to a nested dictionary. + disk. Otherwise reads the JSON file as a pandas DataFrame, transposes + it, and returns it as a table artifact with the column names in an + index column. Parameters ---------- exploration_path : str - Path to the JSON file saved by - ``save_notebook``. + Path to the JSON file saved by ``save_notebook``. options : Dict[str, Any] - Rendering options from the frontend - (unused). + Rendering options from the frontend (unused). Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (Plotly JSON string - when plotting, or nested dict of the correlation matrix otherwise), - ``"type"`` (``"plotly_json"`` when plotting, or ``"tabular"`` - otherwise), and ``"config"`` (empty dict when plotting, or - ``{"orient": "dict"}`` otherwise). + List[Artifact] + A single-element list with the Plotly artifact of the heatmap + when ``self.plot`` is ``True``, or the table artifact of the + correlation matrix otherwise. """ - from pathlib import Path - - import numpy as np - import pandas as pd - if self.plot: - resultType = "plotly_json" with open(exploration_path, "r", encoding="utf-8") as f: result = f.read() - return {"type": resultType, "data": result, "config": {}} + return [PlotlyArtifact(payload=result)] - resultType = "tabular" - config = {"orient": "dict"} + from pathlib import Path - path = Path(exploration_path) + import numpy as np + import pandas as pd - result = pd.read_json(path).replace({np.nan: None}).T.to_dict(orient="dict") - return {"type": resultType, "data": result, "config": config} + matrix = pd.read_json(Path(exploration_path)).replace({np.nan: None}).T + return [ + TableArtifact( + payload=TablePayload( + columns=["index", *matrix.columns.astype(str)], + rows=[ + [str(index), *row] + for index, row in zip( + matrix.index, matrix.to_numpy().tolist(), strict=True + ) + ], + ) + ) + ] diff --git a/DashAI/back/exploration/explorers/cov_matrix.py b/DashAI/back/exploration/explorers/cov_matrix.py index 69282628e..5a2768e49 100644 --- a/DashAI/back/exploration/explorers/cov_matrix.py +++ b/DashAI/back/exploration/explorers/cov_matrix.py @@ -1,5 +1,11 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import ( + Artifact, + PlotlyArtifact, + TableArtifact, + TablePayload, +) from DashAI.back.core.schema_fields import bool_field, int_field, schema_field from DashAI.back.core.utils import MultilingualString from DashAI.back.dependencies.database.models import Explorer, Notebook @@ -317,46 +323,49 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved covariance result for the frontend. When ``self.plot`` is ``True``, reads the raw Plotly JSON string from - disk. Otherwise reads the JSON file as a pandas DataFrame and converts - it to a nested dictionary. + disk. Otherwise reads the JSON file as a pandas DataFrame, transposes + it, and returns it as a table artifact with the column names in an + index column. Parameters ---------- exploration_path : str - Path to the JSON file saved by - ``save_notebook``. + Path to the JSON file saved by ``save_notebook``. options : Dict[str, Any] - Rendering options from the frontend - (unused). + Rendering options from the frontend (unused). Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (Plotly JSON string - when plotting, or nested dict of the covariance matrix - otherwise), ``"type"`` (``"plotly_json"`` when plotting, or - ``"tabular"`` otherwise), and ``"config"`` (empty dict when - plotting, or ``{"orient": "dict"}`` otherwise). + List[Artifact] + A single-element list with the Plotly artifact of the heatmap + when ``self.plot`` is ``True``, or the table artifact of the + covariance matrix otherwise. """ - from pathlib import Path - - import numpy as np - import pandas as pd - if self.plot: - resultType = "plotly_json" with open(exploration_path, "r", encoding="utf-8") as f: result = f.read() - return {"type": resultType, "data": result, "config": {}} + return [PlotlyArtifact(payload=result)] - resultType = "tabular" - config = {"orient": "dict"} + from pathlib import Path - path = Path(exploration_path) + import numpy as np + import pandas as pd - result = pd.read_json(path).replace({np.nan: None}).T.to_dict(orient="dict") - return {"type": resultType, "data": result, "config": config} + matrix = pd.read_json(Path(exploration_path)).replace({np.nan: None}).T + return [ + TableArtifact( + payload=TablePayload( + columns=["index", *matrix.columns.astype(str)], + rows=[ + [str(index), *row] + for index, row in zip( + matrix.index, matrix.to_numpy().tolist(), strict=True + ) + ], + ) + ) + ] diff --git a/DashAI/back/exploration/explorers/density_heatmap.py b/DashAI/back/exploration/explorers/density_heatmap.py index b9d0fd3f5..8d874fa5f 100644 --- a/DashAI/back/exploration/explorers/density_heatmap.py +++ b/DashAI/back/exploration/explorers/density_heatmap.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import int_field, none_type, schema_field from DashAI.back.core.utils import MultilingualString from DashAI.back.dependencies.database.models import Explorer, Notebook @@ -197,7 +198,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved density heatmap for the frontend. Parameters @@ -209,15 +210,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - resultType = "plotly_json" - config = {} - with open(exploration_path, "r", encoding="utf-8") as f: result = f.read() - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/describe_explorer.py b/DashAI/back/exploration/explorers/describe_explorer.py index 17344fa6f..d9c4874ef 100644 --- a/DashAI/back/exploration/explorers/describe_explorer.py +++ b/DashAI/back/exploration/explorers/describe_explorer.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, TableArtifact, TablePayload from DashAI.back.core.schema_fields import ( enum_field, none_type, @@ -327,43 +328,41 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved statistical summary for the frontend. Reads the JSON file written by ``save_notebook``, transposes the - DataFrame so that statistics are keys, and converts it to a nested - dictionary. + DataFrame so that statistics become columns, and returns it as a + table artifact with the feature names in an index column. Parameters ---------- exploration_path : str - Path to the JSON file saved by - ``save_notebook``. + Path to the JSON file saved by ``save_notebook``. options : Dict[str, Any] - Rendering options from the frontend. - Supports ``"orientation"`` (str, default ``"dict"``), which is - forwarded to ``pandas.DataFrame.to_dict``. + Rendering options from the frontend (unused). Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (nested dict of - the transposed describe output in the requested orientation), - ``"type"`` (``"tabular"``), and ``"config"`` (dict containing - ``{"orient": }``). + List[Artifact] + A single-element list with the table artifact of the summary. """ from pathlib import Path import numpy as np import pandas as pd - resultType = "tabular" - orientation = options.get("orientation", "dict") - config = {"orient": orientation} - - path = Path(exploration_path) - - result = ( - pd.read_json(path).replace({np.nan: None}).T.to_dict(orient=orientation) - ) - return {"type": resultType, "data": result, "config": config} + summary = pd.read_json(Path(exploration_path)).replace({np.nan: None}).T + return [ + TableArtifact( + payload=TablePayload( + columns=["index", *summary.columns.astype(str)], + rows=[ + [str(index), *row] + for index, row in zip( + summary.index, summary.to_numpy().tolist(), strict=True + ) + ], + ) + ) + ] diff --git a/DashAI/back/exploration/explorers/ecdf_plot.py b/DashAI/back/exploration/explorers/ecdf_plot.py index 4f4d6941d..68c1c6ee0 100644 --- a/DashAI/back/exploration/explorers/ecdf_plot.py +++ b/DashAI/back/exploration/explorers/ecdf_plot.py @@ -1,6 +1,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Union +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( enum_field, int_field, @@ -348,7 +349,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved ECDF plot for the frontend. Parameters @@ -360,17 +361,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - import plotly.io as pio + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = pio.read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/histogram_plot.py b/DashAI/back/exploration/explorers/histogram_plot.py index 800a12001..d04caf1c5 100644 --- a/DashAI/back/exploration/explorers/histogram_plot.py +++ b/DashAI/back/exploration/explorers/histogram_plot.py @@ -1,6 +1,7 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Union +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( enum_field, int_field, @@ -360,7 +361,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved histogram for the frontend. Parameters @@ -372,17 +373,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - import plotly.io as pio + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = pio.read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/multibox_plot.py b/DashAI/back/exploration/explorers/multibox_plot.py index 0bbe310fb..8a65dc804 100644 --- a/DashAI/back/exploration/explorers/multibox_plot.py +++ b/DashAI/back/exploration/explorers/multibox_plot.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( bool_field, enum_field, @@ -337,7 +338,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved multi-column box plot for the frontend. Parameters @@ -349,17 +350,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - from plotly.io import read_json + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/parallel_categories.py b/DashAI/back/exploration/explorers/parallel_categories.py index 38db20290..beb2a24c6 100644 --- a/DashAI/back/exploration/explorers/parallel_categories.py +++ b/DashAI/back/exploration/explorers/parallel_categories.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Union +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( int_field, none_type, @@ -220,7 +221,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved parallel categories plot for the frontend. Parameters @@ -232,17 +233,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - import plotly.io as pio + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = pio.read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/parallel_cordinates.py b/DashAI/back/exploration/explorers/parallel_cordinates.py index 9d93a783e..2428da9e2 100644 --- a/DashAI/back/exploration/explorers/parallel_cordinates.py +++ b/DashAI/back/exploration/explorers/parallel_cordinates.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, List, Union +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( int_field, none_type, @@ -222,7 +223,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved parallel coordinates plot for the frontend. Parameters @@ -234,17 +235,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - import plotly.io as pio + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = pio.read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/row_explorer.py b/DashAI/back/exploration/explorers/row_explorer.py index 051dfd541..35e39cdfd 100644 --- a/DashAI/back/exploration/explorers/row_explorer.py +++ b/DashAI/back/exploration/explorers/row_explorer.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, TableArtifact, TablePayload from DashAI.back.core.schema_fields import bool_field, int_field, schema_field from DashAI.back.core.utils import MultilingualString from DashAI.back.dependencies.database.models import Explorer, Notebook @@ -237,40 +238,41 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved row sample for the frontend. - Reads the JSON file written by ``save_notebook`` and converts it to a - nested dictionary for tabular display. + Reads the JSON file written by ``save_notebook`` and returns it as a + table artifact with the row labels in an index column. Parameters ---------- exploration_path : str - Path to the JSON file saved by - ``save_notebook``. + Path to the JSON file saved by ``save_notebook``. options : Dict[str, Any] - Rendering options from the frontend. - Supports ``"orientation"`` (str, default ``"dict"``), which is - forwarded to ``pandas.DataFrame.to_dict``. + Rendering options from the frontend (unused). Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (nested dict of - the sampled rows in the requested orientation), ``"type"`` - (``"tabular"``), and ``"config"`` (dict containing - ``{"orient": }``). + List[Artifact] + A single-element list with the table artifact of the sampled + rows. """ from pathlib import Path import numpy as np import pandas as pd - resultType = "tabular" - orientation = options.get("orientation", "dict") - config = {"orient": orientation} - - path = Path(exploration_path) - - result = pd.read_json(path).replace({np.nan: None}).to_dict(orient=orientation) - return {"type": resultType, "data": result, "config": config} + sample = pd.read_json(Path(exploration_path)).replace({np.nan: None}) + return [ + TableArtifact( + payload=TablePayload( + columns=["index", *sample.columns.astype(str)], + rows=[ + [str(index), *row] + for index, row in zip( + sample.index, sample.to_numpy().tolist(), strict=True + ) + ], + ) + ) + ] diff --git a/DashAI/back/exploration/explorers/scatter_matrix.py b/DashAI/back/exploration/explorers/scatter_matrix.py index 1569abc21..0ed3ef6f4 100644 --- a/DashAI/back/exploration/explorers/scatter_matrix.py +++ b/DashAI/back/exploration/explorers/scatter_matrix.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( int_field, none_type, @@ -270,7 +271,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved scatter matrix for the frontend. Parameters @@ -282,17 +283,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - import plotly.io as pio + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = pio.read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/scatter_plot.py b/DashAI/back/exploration/explorers/scatter_plot.py index 4c79e5240..afdc0d505 100644 --- a/DashAI/back/exploration/explorers/scatter_plot.py +++ b/DashAI/back/exploration/explorers/scatter_plot.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact from DashAI.back.core.schema_fields import ( int_field, none_type, @@ -298,7 +299,7 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved scatter plot for the frontend. Parameters @@ -310,17 +311,11 @@ def get_results( Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (JSON-serialized - Plotly figure), ``"type"`` (``"plotly_json"``), and - ``"config"`` (empty dict). + List[Artifact] + A single-element list with the plotly artifact of the saved + figure. """ - from plotly.io import read_json + with open(exploration_path, "r", encoding="utf-8") as f: + result = f.read() - resultType = "plotly_json" - config = {} - - result = read_json(exploration_path) - result = result.to_json() - - return {"data": result, "type": resultType, "config": config} + return [PlotlyArtifact(payload=result)] diff --git a/DashAI/back/exploration/explorers/wordcloud.py b/DashAI/back/exploration/explorers/wordcloud.py index 7f8f76724..8d38a40d6 100644 --- a/DashAI/back/exploration/explorers/wordcloud.py +++ b/DashAI/back/exploration/explorers/wordcloud.py @@ -1,5 +1,6 @@ -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, List +from DashAI.back.core.artifacts import Artifact, ImageArtifact, ImagePayload from DashAI.back.core.schema_fields import ( int_field, none_type, @@ -222,39 +223,26 @@ def save_notebook( def get_results( self, exploration_path: str, options: Dict[str, Any] - ) -> Dict[str, Any]: + ) -> List[Artifact]: """Load and return the saved word cloud image for the frontend. - Reads the PNG file written by ``save_notebook`` and encodes it as a - base64 string for transmission to the frontend. + Reads the PNG file written by ``save_notebook`` and wraps its bytes + in an image artifact. Parameters ---------- exploration_path : str - Path to the PNG file saved by - ``save_notebook``. + Path to the PNG file saved by ``save_notebook``. options : Dict[str, Any] - Rendering options from the frontend - (unused). + Rendering options from the frontend (unused). Returns ------- - Dict[str, Any] - Dictionary with keys ``"data"`` (base64-encoded - UTF-8 string of the PNG image), ``"type"`` - (``"image_base64"``), and ``"config"`` (empty dict). + List[Artifact] + A single-element list with the image artifact of the word + cloud. """ - import base64 - - resultType = "image_base64" - config = {} - - # Load image with open(exploration_path, "rb") as f: result = f.read() - # encode image to base64 - result = base64.b64encode(result).decode("utf-8") - - # Return image - return {"data": result, "type": resultType, "config": config} + return [ImageArtifact(payload=ImagePayload(data=result))] diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 49ea258f1..e1c00cf2d 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -58,8 +58,12 @@ from DashAI.back.converters.simple_converters.character_replacer import ( CharacterReplacer, ) +from DashAI.back.converters.simple_converters.column_arithmetic import ColumnArithmetic +from DashAI.back.converters.simple_converters.column_concat import ColumnConcat from DashAI.back.converters.simple_converters.column_remover import ColumnRemover from DashAI.back.converters.simple_converters.nan_remover import NanRemover +from DashAI.back.converters.simple_converters.numeric_expansion import NumericExpansion +from DashAI.back.converters.simple_converters.type_cast import TypeCast # DataLoaders from DashAI.back.dataloaders.classes.arff_dataloader import ARFFDataLoader @@ -76,11 +80,31 @@ from DashAI.back.dataset_sources.zenodo_dataset_source import ZenodoDatasetSource # Explainers +from DashAI.back.explainability.explainers.contrastive_shap import ContrastiveShap +from DashAI.back.explainability.explainers.dice_counterfactual import ( + DiceCounterfactual, +) +from DashAI.back.explainability.explainers.grad_cam import GradCam from DashAI.back.explainability.explainers.kernel_shap import KernelShap +from DashAI.back.explainability.explainers.lime_text import LimeText +from DashAI.back.explainability.explainers.nearest_counterfactual import ( + NearestCounterfactual, +) +from DashAI.back.explainability.explainers.occlusion_saliency import OcclusionSaliency from DashAI.back.explainability.explainers.partial_dependence import PartialDependence from DashAI.back.explainability.explainers.permutation_feature_importance import ( PermutationFeatureImportance, ) +from DashAI.back.explainability.explainers.regression_kernel_shap import ( + RegressionKernelShap, +) +from DashAI.back.explainability.explainers.regression_partial_dependence import ( + RegressionPartialDependence, +) +from DashAI.back.explainability.explainers.regression_permutation_feature_importance import ( # noqa: E501 + RegressionPermutationFeatureImportance, +) +from DashAI.back.explainability.explainers.token_ablation import TokenAblation # Explorers from DashAI.back.exploration.explorers.box_plot import BoxPlotExplorer @@ -103,6 +127,7 @@ from DashAI.back.exploration.explorers.wordcloud import WordcloudExplorer # Jobs +from DashAI.back.job.component_download_job import ComponentDownloadJob from DashAI.back.job.converter_job import ConverterJob from DashAI.back.job.datafile_job import DatafileJob from DashAI.back.job.dataset_job import DatasetJob @@ -115,10 +140,12 @@ # Metrics from DashAI.back.metrics.classification.accuracy import Accuracy +from DashAI.back.metrics.classification.balanced_accuracy import BalancedAccuracy from DashAI.back.metrics.classification.cohen_kappa import CohenKappa from DashAI.back.metrics.classification.f1 import F1 from DashAI.back.metrics.classification.hamming_distance import HammingDistance from DashAI.back.metrics.classification.log_loss import LogLoss +from DashAI.back.metrics.classification.matthews_corrcoef import MatthewsCorrCoef from DashAI.back.metrics.classification.precision import Precision from DashAI.back.metrics.classification.recall import Recall from DashAI.back.metrics.classification.roc_auc import ROCAUC @@ -144,11 +171,21 @@ from DashAI.back.models.hugging_face.deberta_v3_transformer import DebertaV3Transformer from DashAI.back.models.hugging_face.distilbert_transformer import DistilBertTransformer from DashAI.back.models.hugging_face.electra_transformer import ElectraTransformer -from DashAI.back.models.hugging_face.llama_model import LlamaModel +from DashAI.back.models.hugging_face.llama_model import ( + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, +) from DashAI.back.models.hugging_face.m2m100_transformer import M2M100Transformer from DashAI.back.models.hugging_face.minilm_transformer import MiniLMTransformer -from DashAI.back.models.hugging_face.mistral_model import MistralModel -from DashAI.back.models.hugging_face.mixtral_model import MixtralModel +from DashAI.back.models.hugging_face.mistral_model import ( + Mistral7BInstructV03, + MistralNemoInstruct2407, +) +from DashAI.back.models.hugging_face.mixtral_model import ( + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, +) from DashAI.back.models.hugging_face.modernbert_transformer import ModernBertTransformer from DashAI.back.models.hugging_face.multilingual_bert_transformer import ( MultilingualBertTransformer, @@ -163,8 +200,8 @@ from DashAI.back.models.hugging_face.opus_mt_en_fr_transformer import ( OpusMtEnFrTransformer, ) -from DashAI.back.models.hugging_face.opus_mt_en_pt_transformer import ( - OpusMtEnPtTransformer, +from DashAI.back.models.hugging_face.opus_mt_en_roa_transformer import ( + OpusMtEnRoaTransformer, ) from DashAI.back.models.hugging_face.opus_mt_es_en_transformer import ( OpusMtEsENTransformer, @@ -172,8 +209,14 @@ from DashAI.back.models.hugging_face.opus_mt_fr_en_transformer import ( OpusMtFrEnTransformer, ) -from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigmaModel -from DashAI.back.models.hugging_face.qwen_model import QwenModel +from DashAI.back.models.hugging_face.opus_mt_roa_en_transformer import ( + OpusMtRoaEnTransformer, +) +from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigma +from DashAI.back.models.hugging_face.qwen_model import ( + Qwen25_05BInstruct, + Qwen25_15BInstruct, +) from DashAI.back.models.hugging_face.roberta_transformer import RobertaTransformer from DashAI.back.models.hugging_face.sd15_depth_controlnet_model import ( SD15DepthControlNetModel, @@ -188,27 +231,41 @@ SDXLCannyControlNetModel, ) from DashAI.back.models.hugging_face.sdxl_turbo_model import SDXLTurboModel -from DashAI.back.models.hugging_face.smol_lm_model import SmolLMModel +from DashAI.back.models.hugging_face.smol_lm_model import ( + SmolLM2_17BInstruct, + SmolLM2_360MInstruct, +) from DashAI.back.models.hugging_face.stable_diffusion_v1_depth_controlnet import ( StableDiffusionXLV1ControlNet, ) from DashAI.back.models.hugging_face.stable_diffusion_v2_model import ( - StableDiffusionV2Model, + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, ) from DashAI.back.models.hugging_face.stable_diffusion_v3_model import ( - StableDiffusionV3Model, + StableDiffusion3Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, + StableDiffusion35Medium, ) from DashAI.back.models.hugging_face.stable_diffusion_xl_model import ( - StableDiffusionXLModel, + RealVisXLV4, + StableDiffusionXL, ) from DashAI.back.models.hugging_face.t5_small_transformer import T5SmallTransformer -from DashAI.back.models.hugging_face.tongyi_z_image_model import TongyiZImageModel +from DashAI.back.models.hugging_face.tongyi_z_image_model import ( + TongyiZImage, + TongyiZImageTurbo, +) from DashAI.back.models.hugging_face.xlm_roberta_transformer import ( XlmRobertaTransformer, ) from DashAI.back.models.hugging_face.xlnet_transformer import XlnetTransformer from DashAI.back.models.lenet5_image_classifier import LeNet5ImageClassifier from DashAI.back.models.mlp_image_classifier import MLPImageClassifier +from DashAI.back.models.pymc.bart_regression import BARTRegression from DashAI.back.models.resnet18_image_classifier import ResNet18ImageClassifier from DashAI.back.models.resnet50_image_classifier import ResNet50ImageClassifier from DashAI.back.models.scikit_learn.adaboost_classifier import AdaBoostClassifier @@ -325,6 +382,7 @@ def get_initial_components(): BertinTransformer, BetoTransformer, BayesianRidgeRegression, + BARTRegression, DebertaV3Transformer, DecisionTreeClassifier, DecisionTreeRegression, @@ -345,12 +403,16 @@ def get_initial_components(): LinearRegression, LinearSVCClassifier, LinearSVR, - LlamaModel, + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, LogisticRegression, M2M100Transformer, MiniLMTransformer, - MistralModel, - MixtralModel, + Mistral7BInstructV03, + MistralNemoInstruct2407, + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, MultilingualBertTransformer, MLPClassifier, MLPRegression, @@ -359,11 +421,13 @@ def get_initial_components(): OpusMtEnDeTransformer, OpusMtEnESTransformer, OpusMtEnFrTransformer, - OpusMtEnPtTransformer, + OpusMtEnRoaTransformer, OpusMtEsENTransformer, OpusMtFrEnTransformer, - PixArtSigmaModel, - QwenModel, + PixArtSigma, + Qwen25_05BInstruct, + Qwen25_15BInstruct, + OpusMtRoaEnTransformer, RandomForestClassifier, RobertaTransformer, RandomForestRegression, @@ -374,16 +438,25 @@ def get_initial_components(): SDXLCannyControlNetModel, SDXLTurboModel, SGDClassifier, - SmolLMModel, - StableDiffusionV2Model, - StableDiffusionV3Model, - StableDiffusionXLModel, + SmolLM2_360MInstruct, + SmolLM2_17BInstruct, + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, + StableDiffusion3Medium, + StableDiffusion35Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, + StableDiffusionXL, + RealVisXLV4, StableDiffusionXLV1ControlNet, SVC, SVR, T5SmallTransformer, TfIdfLogRegTextClassificationModel, - TongyiZImageModel, + TongyiZImage, + TongyiZImageTurbo, XlmRobertaTransformer, XlnetTransformer, MLPImageClassifier, @@ -405,6 +478,7 @@ def get_initial_components(): # Metrics F1, Accuracy, + BalancedAccuracy, Precision, Recall, Bleu, @@ -420,10 +494,12 @@ def get_initial_components(): LogLoss, HammingDistance, CohenKappa, + MatthewsCorrCoef, # Optimizers OptunaOptimizer, HyperOptOptimizer, # Jobs + ComponentDownloadJob, DatafileJob, ExplainerJob, ModelJob, @@ -434,9 +510,19 @@ def get_initial_components(): GenerativeJob, PipelineJob, # Explainers + ContrastiveShap, + DiceCounterfactual, + GradCam, KernelShap, + LimeText, + NearestCounterfactual, + OcclusionSaliency, PartialDependence, PermutationFeatureImportance, + RegressionKernelShap, + RegressionPartialDependence, + RegressionPermutationFeatureImportance, + TokenAblation, # Explorers DescribeExplorer, ScatterPlotExplorer, @@ -456,6 +542,10 @@ def get_initial_components(): ColumnRemover, NanRemover, CharacterReplacer, + ColumnArithmetic, + ColumnConcat, + NumericExpansion, + TypeCast, FastICA, IncrementalPCA, PCA, diff --git a/DashAI/back/job/base_job.py b/DashAI/back/job/base_job.py index f1fcaaf52..53a521db4 100644 --- a/DashAI/back/job/base_job.py +++ b/DashAI/back/job/base_job.py @@ -1,7 +1,10 @@ """Base Job abstract class.""" +import logging from abc import ABCMeta, abstractmethod -from typing import Final +from typing import Final, Optional + +logger = logging.getLogger(__name__) class BaseJob(metaclass=ABCMeta): @@ -20,6 +23,35 @@ def __init__(self, **kwargs): job_kwargs = kwargs.pop("kwargs", {}) self.kwargs = {**kwargs, **job_kwargs} + def report_progress( + self, fraction: Optional[float], message: Optional[str] = None + ) -> None: + """Report the job's progress to the job queue. + + Jobs opt in by calling this at meaningful checkpoints. It is safe to + call from any job: it never raises and does nothing when the job has no + Huey id (e.g. immediate mode used in tests) or the queue is unavailable. + + Parameters + ---------- + fraction: Optional float + Completion in the range 0-1, or None when the total work is unknown + (the frontend then shows an indeterminate bar). + message: Optional str + Short description of the current phase. + """ + try: + from kink import di + + huey_id = self.kwargs.get("huey_id") + if not huey_id: + return + + progress = None if fraction is None else max(0.0, min(1.0, fraction)) * 100 + di["job_queue"].report_progress(huey_id, progress, message) + except Exception as e: # pragma: no cover - progress must never break a job + logger.debug(f"Could not report job progress: {e}") + @abstractmethod def set_status_as_delivered(self) -> None: """Set the status of the job as delivered.""" diff --git a/DashAI/back/job/component_download_job.py b/DashAI/back/job/component_download_job.py new file mode 100644 index 000000000..191e96b63 --- /dev/null +++ b/DashAI/back/job/component_download_job.py @@ -0,0 +1,58 @@ +"""Job that downloads a component's external artifacts.""" + +import logging + +from kink import di + +from DashAI.back.job.base_job import BaseJob, JobError + +log = logging.getLogger(__name__) + + +class ComponentDownloadJob(BaseJob): + """Download the artifacts required by a downloadable component. + + Parameters + ---------- + kwargs : dict + Must contain ``component_name`` (the component class name). + """ + + def set_status_as_delivered(self) -> None: + """No dedicated DB entity; nothing to mark as delivered.""" + + def set_status_as_error(self) -> None: + """No dedicated DB entity; nothing to mark as error.""" + + def get_job_name(self) -> str: + """Return a descriptive name for the job. + + Returns + ------- + str + A human-readable name including the component name. + """ + return f"Download: {self.kwargs.get('component_name', 'component')}" + + def run(self) -> None: + """Resolve the component and download its artifacts, reporting progress. + + Raises + ------ + JobError + If the component is not registered or does not require a download. + """ + component_registry = di["component_registry"] + name = self.kwargs["component_name"] + + try: + component_class = component_registry[name]["class"] + except KeyError as e: + raise JobError(f"Component {name} is not registered") from e + + if not getattr(component_class, "REQUIRES_DOWNLOAD", False): + raise JobError(f"Component {name} does not require a download") + + self.report_progress(0.0, "Starting download") + component_class.download(self.report_progress) + self.report_progress(1.0, "Download complete") diff --git a/DashAI/back/job/converter_job.py b/DashAI/back/job/converter_job.py index 97cb18c06..fcbfc4d68 100644 --- a/DashAI/back/job/converter_job.py +++ b/DashAI/back/job/converter_job.py @@ -57,14 +57,23 @@ def _rebuild_dataset_with_transformed_columns( original_columns = base.column_names transformed_cols = transformed.column_names - removed_cols = [col for col in scope_column_names if col not in transformed_cols] - replacement_cols = [col for col in scope_column_names if col in transformed_cols] - new_cols = [col for col in transformed_cols if col not in scope_column_names] + transformed_cols_set = set(transformed_cols) + scope_column_names_set = set(scope_column_names) + + removed_cols = [ + col for col in scope_column_names if col not in transformed_cols_set + ] + replacement_cols = [ + col for col in scope_column_names if col in transformed_cols_set + ] + new_cols = [col for col in transformed_cols if col not in scope_column_names_set] + + removed_cols_set = set(removed_cols) new_columns_order = [] seen_cols = set() for col in original_columns: - if col in removed_cols: + if col in removed_cols_set: continue if col not in seen_cols: new_columns_order.append(col) @@ -83,10 +92,10 @@ def _rebuild_dataset_with_transformed_columns( updated_arrays = {} for col in replacement_cols: - if col in transformed.arrow_table.column_names: + if col in transformed_cols_set: updated_arrays[col] = transformed.arrow_table[col] for col, unique_col in col_name_mapping.items(): - if col in transformed.arrow_table.column_names: + if col in transformed_cols_set: updated_arrays[unique_col] = transformed.arrow_table[col] updated_types = base.types.copy() @@ -233,6 +242,8 @@ def instantiate_converters( log.exception(e) raise JobError("Error loading converter info") from e + self.report_progress(0.1, "Loading dataset") + # Get dataset try: dataset_id = converter.notebook.dataset_id @@ -309,11 +320,17 @@ def instantiate_converters( i += 1 # Apply each converter in sequence - for converter_info in converter_instances: + total_converters = len(converter_instances) + for converter_index, converter_info in enumerate(converter_instances): converter_instance = converter_info["instance"] converter_name = converter_info["name"] converter_scope = converter_info["scope"] + # Map converter progress onto the 0.2-0.9 band. + self.report_progress( + 0.2 + 0.7 * (converter_index / max(total_converters, 1)), + f"Applying {converter_name}", + ) log.info(f"Applying converter: {converter_name}") columns_scope = [ @@ -345,10 +362,11 @@ def instantiate_converters( ) if scope_rows_indexes: y_dataset_fit = y_dataset_fit.select(scope_rows_indexes) - - y_full_transform = loaded_dataset.select_columns( - [target_column_name] - ) + y_full_transform = loaded_dataset.select_columns( + [target_column_name] + ) + else: + y_full_transform = y_dataset_fit X_dataset_fit = loaded_dataset.select_columns(scope_column_names) @@ -370,7 +388,14 @@ def instantiate_converters( f"Error fitting converter {converter_name}: {e}" ) from e - X_full_transform = loaded_dataset.select_columns(scope_column_names) + if scope_rows_indexes: + X_full_transform = loaded_dataset.select_columns( + scope_column_names + ) + else: + # Same reuse as above: no row-level fit scope means + # X_dataset_fit already covers the full transform scope. + X_full_transform = X_dataset_fit try: transformed_dataset = converter_instance.transform( @@ -394,6 +419,7 @@ def instantiate_converters( dataset_original_columns = loaded_dataset.column_names + self.report_progress(0.95, "Saving dataset") save_dataset(loaded_dataset, f"{dataset_path}") converter.set_status_as_finished() db.commit() diff --git a/DashAI/back/job/dataset_job.py b/DashAI/back/job/dataset_job.py index 6d9fc925e..4193dc3a3 100644 --- a/DashAI/back/job/dataset_job.py +++ b/DashAI/back/job/dataset_job.py @@ -126,6 +126,8 @@ def run( db.commit() db.refresh(dataset) + self.report_progress(0.1, "Loading data") + if n_sample and dataset.file_path != "": folder_path = Path(dataset.file_path) else: @@ -290,6 +292,8 @@ def run( new_dataset = transform_dataset_with_schema(new_dataset, schema) + self.report_progress(0.5, "Computing metadata") + compute_meta = params.get("compute_metadata", True) extended_keys = ( "general_info", @@ -327,6 +331,8 @@ def run( new_dataset.splits.pop(stale_key, None) gc.collect() + self.report_progress(0.8, "Saving dataset") + dataset_save_path = folder_path / "dataset" log.debug("Saving dataset in %s", str(dataset_save_path)) save_dataset(new_dataset, dataset_save_path) diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index 4863a59f0..390b6e149 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -127,13 +127,15 @@ def _generate_global_explanation( from kink import di + from DashAI.back.core.artifacts import normalize_artifacts + explainer_id: int = self.kwargs["explainer_id"] session_factory = di["session_factory"] config = di["config"] with session_factory() as db: try: explanation = explainer.explain(dataset) - plot = explainer.plot(explanation) + plot = normalize_artifacts(explainer.plot(explanation)) except Exception as e: log.exception(e) raise JobError( @@ -160,6 +162,7 @@ def _generate_global_explanation( try: self.explainer_db.explanation_path = explanation_path self.explainer_db.plot_path = plot_path + self.explainer_db.plot_overrides = None db.commit() except Exception as e: log.exception(e) @@ -175,7 +178,6 @@ def _generate_local_explanation( splits: Dict[str, Any], task: BaseTask, same_dataset: bool, - trained_model: BaseModel, ) -> None: import json import os @@ -184,9 +186,11 @@ def _generate_local_explanation( from datasets import DatasetDict from kink import di + from DashAI.back.core.artifacts import normalize_artifacts from DashAI.back.dataloaders.classes.dashai_dataset import ( load_dataset, prepare_for_model_session, + save_dataset, select_columns, split_dataset, ) @@ -211,57 +215,117 @@ def _generate_local_explanation( f"Can not load instance from path {instance.file_path}", ) from e try: - prepared_instance = task.prepare_for_task( - loaded_instance, - input_columns=self.input_columns, - output_columns=self.output_columns, - ) - - split = self.explainer_db.scope.get("split") - if split not in ["train", "test", "val", "all"]: - raise JobError(f"{split} is not a valid split") - - if split != "all": - if not same_dataset: - if isinstance(splits, str): - splits = json.loads(splits) - prepared_dataset_dict, splits = prepare_for_model_session( - dataset=prepared_instance, - splits=splits, - output_columns=self.output_columns, + # The data source is selected via scope["mode"]. It defaults to + # "split" so explainers created before this field existed keep + # their original split + percentage behavior. + mode = self.explainer_db.scope.get("mode", "split") + + if mode == "manual": + # Build the instances from values the user typed in by hand, + # reusing the same conversion the manual prediction flow uses. + # The rows (and any image files rewritten by the job endpoint) + # travel in the job kwargs, not in scope. + manual_input_data = self.kwargs.get("manual_input_data") or [] + if not manual_input_data: + raise JobError( + "No manual input data provided for the explanation" ) - split_key = "validation" if split == "val" else split - prepared_instance = prepared_dataset_dict[split_key] - else: - prepared_instance = split_dataset( - prepared_instance, - train_indexes=splits["train_indexes"], - test_indexes=splits["test_indexes"], - val_indexes=splits["val_indexes"], - ) - split_key = "validation" if split == "val" else split - prepared_instance = prepared_instance[split_key] + prepared_instance = task.process_manual_input( + manual_input_data, + f"{instance.file_path}/dataset", + ) + # Manual input carries only the input columns (no target), so + # keep just those instead of the standard input/output split. + # select_columns returns a DashAIDataset (same shape the + # split path produces), which is what the explainers expect. + X = prepared_instance.select_columns(self.input_columns) + else: + prepared_instance = task.prepare_for_task( + loaded_instance, + input_columns=self.input_columns, + output_columns=self.output_columns, + ) - prepared_instance = prepared_instance.select( - range( - max( + if mode == "rows": + # Explain a set of rows the user marked in the table. + # Indexes are over the whole dataset (the split does not + # apply in this mode). + row_indexes = self.explainer_db.scope.get("row_indexes") or [] + valid_indexes = [ + i + for i in row_indexes + if isinstance(i, int) + and 0 <= i < prepared_instance.num_rows + ] + if row_indexes and not valid_indexes: + raise JobError( + "No valid row indexes provided for the explanation" + ) + if valid_indexes: + prepared_instance = prepared_instance.select(valid_indexes) + else: + split = self.explainer_db.scope.get("split") + if split not in ["train", "test", "val", "all"]: + raise JobError(f"{split} is not a valid split") + + if split != "all": + if not same_dataset: + if isinstance(splits, str): + splits = json.loads(splits) + ( + prepared_dataset_dict, + splits, + ) = prepare_for_model_session( + dataset=prepared_instance, + splits=splits, + output_columns=self.output_columns, + ) + split_key = "validation" if split == "val" else split + prepared_instance = prepared_dataset_dict[split_key] + else: + prepared_instance = split_dataset( + prepared_instance, + train_indexes=splits["train_indexes"], + test_indexes=splits["test_indexes"], + val_indexes=splits["val_indexes"], + ) + split_key = "validation" if split == "val" else split + prepared_instance = prepared_instance[split_key] + + n_rows = max( 1, int( prepared_instance.num_rows * self.explainer_db.scope.get("percentage") / 100 ), - ), + ) + # When "shuffle" is set the percentage is taken as a random + # sample of the split; otherwise it is the leading rows. + if self.explainer_db.scope.get("shuffle"): + prepared_instance = prepared_instance.shuffle(seed=42) + prepared_instance = prepared_instance.select(range(n_rows)) + + prepared_instance = DatasetDict({"train": prepared_instance}) + X, _ = select_columns( + prepared_instance, + self.input_columns, + self.output_columns, ) + # Persist the original selected rows (the model input for each + # explained instance) as a DashAIDataset before the model's own + # preprocessing runs, so the frontend can read them back with + # the existing dataset endpoints. + input_source = X["train"] if isinstance(X, DatasetDict) else X + input_dataset_path = os.path.join( + config["EXPLANATIONS_PATH"], + f"local_explanation_input_{explainer_id}", ) - - prepared_instance = DatasetDict({"train": prepared_instance}) - X, _ = select_columns( - prepared_instance, - self.input_columns, - self.output_columns, - ) - X = trained_model.prepare_dataset(X, is_fit=False) + save_dataset(input_source, os.path.join(input_dataset_path, "dataset")) + # The instances are handed over unprepared, the same way the + # prediction job calls model.predict: the model applies its own + # preprocessing. Explainers that need the model feature space + # ask for it with prepare_model_input. except Exception as e: log.exception(e) @@ -271,14 +335,16 @@ def _generate_local_explanation( ) from e try: explanation = explainer.explain_instance(X) - plots = explainer.plot(explanation) + plots = normalize_artifacts( + explainer.plot(explanation), create_grouped=True + ) except Exception as e: log.exception(e) raise JobError( "Failed to generate the explanation", ) from e try: - explanation_filename = f"local_explanation_{explainer_id}.json" + explanation_filename = f"local_explanation_{explainer_id}.pickle" explanation_path = os.path.join( config["EXPLANATIONS_PATH"], explanation_filename ) @@ -298,6 +364,8 @@ def _generate_local_explanation( try: self.explainer_db.explanation_path = explanation_path self.explainer_db.plots_path = plots_path + self.explainer_db.input_dataset_path = input_dataset_path + self.explainer_db.plot_overrides = None db.commit() except Exception as e: log.exception(e) @@ -446,10 +514,10 @@ def run( test_indexes=splits["test_indexes"], val_indexes=splits["val_indexes"], ) - for split_name in data_x: - data_x[split_name] = trained_model.prepare_dataset( - data_x[split_name], is_fit=False - ) + # Inputs stay unprepared (see the note in the local + # explanation path); targets are encoded because explainers + # compare them against the model's class indexes. + for split_name in data_y: data_y[split_name] = trained_model.prepare_output( data_y[split_name], is_fit=False ) @@ -485,7 +553,6 @@ def run( splits=splits, task=task, same_dataset=same_dataset, - trained_model=trained_model, ) else: raise JobError(f"{explainer_scope} is an invalid explainer type") diff --git a/DashAI/back/job/generative_job.py b/DashAI/back/job/generative_job.py index e52c58a72..bbf5de47e 100644 --- a/DashAI/back/job/generative_job.py +++ b/DashAI/back/job/generative_job.py @@ -151,8 +151,20 @@ def run( model_class = component_registry[generative_session.model_name][ "class" ] + if ( + getattr(model_class, "REQUIRES_DOWNLOAD", False) + and not model_class.is_downloaded() + ): + raise JobError( + f"Model {generative_session.model_name} is not downloaded." + " Download it before use." + ) params = generative_session.parameters model: BaseGenerativeModel = model_class(**params) + except JobError: + generative_process.set_status_as_error() + db.commit() + raise except Exception as e: log.exception(e) generative_process.set_status_as_error() diff --git a/DashAI/back/job/model_job.py b/DashAI/back/job/model_job.py index 583c6b5fb..3ffb0a799 100644 --- a/DashAI/back/job/model_job.py +++ b/DashAI/back/job/model_job.py @@ -7,6 +7,7 @@ from DashAI.back.core.enums.metrics import LevelEnum, SplitEnum from DashAI.back.dependencies.database.models import Dataset, Metric, ModelSession, Run +from DashAI.back.dependencies.downloads.nested import missing_downloads from DashAI.back.job.base_job import BaseJob, JobError from DashAI.back.metrics.base_metric import BaseMetric from DashAI.back.models.base_model import BaseModel @@ -115,6 +116,7 @@ def run( run: Run = db.get(Run, run_id) run.huey_id = self.kwargs.get("huey_id", None) db.commit() + self.report_progress(0.05, "Preparing data") try: # Get the model session, dataset, task, metrics and splits model_session: ModelSession = db.get(ModelSession, run.model_session_id) @@ -221,6 +223,20 @@ def run( raise JobError( f"Unable to find Model with name {run.model_name} in registry.", ) from e + if getattr(run_model_class, "REQUIRES_DOWNLOAD", False) and not ( + run_model_class.is_downloaded() + ): + raise JobError( + f"Model {run.model_name} is not downloaded. " + "Download it before training." + ) + nested_missing = missing_downloads(run.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise JobError( + "These components are not downloaded. " + f"Download them before training: {names}." + ) try: factory = ModelFactory( run_model_class, @@ -271,6 +287,7 @@ def run( raise JobError( "Connection with the database failed", ) from e + self.report_progress(0.2, "Training") try: # Hyperparameter Tunning plot_paths = [] @@ -300,6 +317,8 @@ def run( db.commit() # Generate hyperparameter plot + from DashAI.back.core.artifacts import normalize_artifacts + trials = optimizer.get_trials_values() plot_filenames, plots = optimizer.create_plots( trials, @@ -307,7 +326,10 @@ def run( n_params=len(run_optimizable_parameters), goal_metric=goal_metric, ) - for filename, plot in zip(plot_filenames, plots, strict=False): + normalized_plots = normalize_artifacts(plots) + for filename, plot in zip( + plot_filenames, normalized_plots, strict=False + ): plot_path = os.path.join(config["RUNS_PATH"], filename) with open(plot_path, "wb") as file: pickle.dump(plot, file) @@ -332,6 +354,7 @@ def run( f"Hyperparameter plot path saving failed {e}", ) from e + self.report_progress(0.85, "Computing metrics") # Calculate metrics at the end of training if not done already try: last_train_metric = ( @@ -370,6 +393,7 @@ def run( f"Metric calculation failed {e}", ) from e + self.report_progress(0.95, "Saving model") try: run_path = os.path.join(config["RUNS_PATH"], str(run.id)) model.save(run_path) diff --git a/DashAI/back/job/predict_job.py b/DashAI/back/job/predict_job.py index efbdee0ca..3bb3c95f9 100644 --- a/DashAI/back/job/predict_job.py +++ b/DashAI/back/job/predict_job.py @@ -305,6 +305,8 @@ def run( prediction.set_status_as_started() db.commit() + self.report_progress(0.1, "Loading model") + dataset_id = prediction.dataset_id # Validate input data @@ -422,7 +424,8 @@ def run( manual_input_data, dataset_trained_path ) - prepared_dataset, y_pred = _run_prediction_pipeline( + self.report_progress(0.4, "Running prediction") + _, y_pred = _run_prediction_pipeline( task=task, trained_model=trained_model, train_dataset=train_dataset, @@ -452,6 +455,8 @@ def run( "Model prediction failed", ) from e + self.report_progress(0.9, "Saving predictions") + # Save Predictions to Arrow file try: # Create unique folder for predictions @@ -460,9 +465,13 @@ def run( full_path = Path(path) / folder_name full_path.mkdir(parents=True, exist_ok=True) - # Add predictions to loaded dataset + output_col = model_session.output_columns[0] + base_columns = [ + col for col in loaded_dataset.column_names if col != output_col + ] + output_dataset = loaded_dataset.select_columns(base_columns) dataset_with_prediction = to_dashai_dataset( - prepared_dataset.add_column(model_session.output_columns[0], y_pred) + output_dataset.add_column(output_col, y_pred) ) # Filter schema from trained dataset diff --git a/DashAI/back/metrics/base_metric.py b/DashAI/back/metrics/base_metric.py index 2dc178620..d55e2200b 100644 --- a/DashAI/back/metrics/base_metric.py +++ b/DashAI/back/metrics/base_metric.py @@ -1,6 +1,6 @@ """Base Metric abstract class.""" -from typing import TYPE_CHECKING, Any, Dict, Final, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Final, Tuple, Union if TYPE_CHECKING: from numpy import ndarray @@ -30,7 +30,6 @@ class BaseMetric: TYPE: Final[str] = "Metric" MAXIMIZE: Final[bool] = False - NORMALIZE_REF: Optional[float] = None metadata: Dict[str, Any] = {} @classmethod @@ -45,7 +44,6 @@ def get_metadata(cls: "BaseMetric") -> Dict[str, Any]: """ meta: Dict[str, Any] = dict(getattr(cls, "metadata", {}) or {}) meta["maximize"] = cls.MAXIMIZE - meta["normalize_ref"] = cls.NORMALIZE_REF return meta diff --git a/DashAI/back/metrics/classification/balanced_accuracy.py b/DashAI/back/metrics/classification/balanced_accuracy.py new file mode 100644 index 000000000..2a1fe0873 --- /dev/null +++ b/DashAI/back/metrics/classification/balanced_accuracy.py @@ -0,0 +1,77 @@ +"""DashAI balanced accuracy classification metric implementation.""" + +from typing import TYPE_CHECKING + +from DashAI.back.core.utils import MultilingualString +from DashAI.back.metrics.classification_metric import ( + ClassificationMetric, + prepare_to_metric, +) + +if TYPE_CHECKING: + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +class BalancedAccuracy(ClassificationMetric): + """Average of recall obtained on each class. + + Balanced Accuracy is the macro-average of recall scores per class. It + avoids the inflated performance estimates that plain accuracy gives on + imbalanced datasets, since each class contributes equally regardless of + how many samples it has. + + :: + + Balanced Accuracy = (1 / C) * sum(recall_c for c in classes) + + Range: [0, 1], higher is better (``MAXIMIZE = True``). + + References + ---------- + - [1] https://scikit-learn.org/stable/modules/generated/sklearn.metrics.balanced_accuracy_score.html + """ + + DESCRIPTION = MultilingualString( + en=("Macro-average of recall per class, best suited for imbalanced datasets."), + es=( + "Promedio macro del recall por clase, más adecuado para " + "datasets desbalanceados." + ), + pt=( + "Média macro do recall por classe, mais adequada para " + "conjuntos de dados desbalanceados." + ), + de=( + "Makro-Durchschnitt des Recalls je Klasse, am besten geeignet für " + "unausgewogene Datensätze." + ), + zh=("按类别宏平均的召回率,最适用于类别不均衡的数据集。"), + ) + + @staticmethod + def score( + true_labels: "DashAIDataset", + probs_pred_labels: "np.ndarray", + ) -> float: + """Calculate the balanced accuracy between true and predicted labels. + + Parameters + ---------- + true_labels : DashAIDataset + A DashAI dataset with labels. + probs_pred_labels : np.ndarray + A two-dimensional matrix in which each column represents a class + and the row values represent the probability that an example belongs + to the class associated with the column. + + Returns + ------- + float + Balanced accuracy score between true labels and predicted labels + """ + from sklearn.metrics import balanced_accuracy_score + + true_labels, pred_labels = prepare_to_metric(true_labels, probs_pred_labels) + return balanced_accuracy_score(true_labels, pred_labels) diff --git a/DashAI/back/metrics/classification/hamming_distance.py b/DashAI/back/metrics/classification/hamming_distance.py index a6da8e7d8..a9af724ed 100644 --- a/DashAI/back/metrics/classification/hamming_distance.py +++ b/DashAI/back/metrics/classification/hamming_distance.py @@ -41,7 +41,6 @@ class HammingDistance(ClassificationMetric): """ MAXIMIZE: bool = False - NORMALIZE_REF: float = 1.0 DESCRIPTION = MultilingualString( en=( "Hamming Distance measures the fraction of " diff --git a/DashAI/back/metrics/classification/matthews_corrcoef.py b/DashAI/back/metrics/classification/matthews_corrcoef.py new file mode 100644 index 000000000..279c4d41c --- /dev/null +++ b/DashAI/back/metrics/classification/matthews_corrcoef.py @@ -0,0 +1,106 @@ +"""DashAI Matthews Correlation Coefficient classification metric implementation.""" + +from typing import TYPE_CHECKING, Optional + +from DashAI.back.core.utils import MultilingualString +from DashAI.back.metrics.classification_metric import ( + ClassificationMetric, + prepare_to_metric, +) + +if TYPE_CHECKING: + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +class MatthewsCorrCoef(ClassificationMetric): + """Correlation between predicted and true labels, robust to class imbalance. + + The Matthews Correlation Coefficient (MCC) is a balanced measure of the + quality of a classification that can be used even when the classes are of + very different sizes. In essence it is the correlation coefficient between + the observed and predicted classifications, computed from the confusion + matrix. + + :: + + MCC = (TP·TN − FP·FN) / + sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN)) + + Range: [-1, 1]. Interpretation: +1 a perfect prediction; 0 no better than + random guessing; -1 total disagreement between prediction and observation. + Because a high score requires the classifier to do well on every class + (all four confusion-matrix quadrants), MCC is regarded as more informative + than accuracy or F1 on imbalanced problems. It generalises to the + multiclass setting via scikit-learn's implementation. + + References + ---------- + - [1] Matthews, B.W. (1975). "Comparison of the predicted and observed + secondary structure of T4 phage lysozyme." Biochimica et Biophysica + Acta (BBA) - Protein Structure, 405(2), 442-451. + - [2] Chicco, D. & Jurman, G. (2020). "The advantages of the Matthews + correlation coefficient (MCC) over F1 score and accuracy in binary + classification evaluation." BMC Genomics, 21(6). + - [3] https://scikit-learn.org/stable/modules/generated/ + sklearn.metrics.matthews_corrcoef.html + """ + + DESCRIPTION = MultilingualString( + en=( + "The Matthews Correlation Coefficient measures the correlation " + "between predicted and true labels, returning a value in [-1, 1] " + "that stays reliable even when the classes are imbalanced." + ), + es=( + "El Coeficiente de Correlación de Matthews mide la correlación " + "entre las etiquetas predichas y verdaderas, devolviendo un valor " + "en [-1, 1] que sigue siendo confiable incluso con clases " + "desbalanceadas." + ), + pt=( + "O Coeficiente de Correlação de Matthews mede a correlação entre " + "os rótulos previstos e verdadeiros, retornando um valor em " + "[-1, 1] que permanece confiável mesmo com classes desbalanceadas." + ), + de=( + "Der Matthews-Korrelationskoeffizient misst die Korrelation " + "zwischen vorhergesagten und wahren Labels und liefert einen Wert " + "in [-1, 1], der auch bei unausgewogenen Klassen zuverlässig bleibt." + ), + zh="马修斯相关系数衡量预测标签与真实标签之间的相关性,返回 [-1, 1] " + "范围内的值,即使在类别不平衡时也保持可靠。", + ) + + @staticmethod + def score( + true_labels: "DashAIDataset", + probs_pred_labels: "np.ndarray", + multiclass: Optional[bool] = None, + ) -> float: + """Calculate the Matthews Correlation Coefficient. + + Parameters + ---------- + true_labels : DashAIDataset + A DashAI dataset with labels. + probs_pred_labels : np.ndarray + A two-dimensional matrix in which each column represents a class + and the row values represent the probability that an example belongs + to the class associated with the column. + multiclass : bool, optional + Whether the task is a multiclass classification. If None, it will be + determined automatically from the number of unique labels. + + Returns + ------- + float + Matthews Correlation Coefficient between true labels and predicted + labels. + """ + from sklearn.metrics import matthews_corrcoef + + true_labels, pred_labels = prepare_to_metric(true_labels, probs_pred_labels) + + return matthews_corrcoef(true_labels, pred_labels) diff --git a/DashAI/back/models/base_generative_model.py b/DashAI/back/models/base_generative_model.py index cff0e18e1..62dbcb9af 100644 --- a/DashAI/back/models/base_generative_model.py +++ b/DashAI/back/models/base_generative_model.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from typing import Any, Final, List, Tuple, Union +from typing import Any, Dict, Final, List, Tuple, Union from DashAI.back.config_object import ConfigObject @@ -15,6 +15,21 @@ class BaseGenerativeModel(ConfigObject, metaclass=ABCMeta): TYPE: Final[str] = "GenerativeModel" + @classmethod + def get_metadata(cls) -> Dict[str, Any]: + """Get metadata values for the current generative model. + + Returns + ------- + Dict[str, Any] + Dictionary indicating whether the model requires a download + before use and the expected download size in bytes. + """ + metadata: Dict[str, Any] = {} + metadata["requires_download"] = bool(getattr(cls, "REQUIRES_DOWNLOAD", False)) + metadata["download_size_bytes"] = getattr(cls, "DOWNLOAD_SIZE_BYTES", None) + return metadata + @abstractmethod def __init__(self, **kwargs): """Initialize the generative model with configuration parameters. diff --git a/DashAI/back/models/base_model.py b/DashAI/back/models/base_model.py index 6cebb7818..36a08a962 100644 --- a/DashAI/back/models/base_model.py +++ b/DashAI/back/models/base_model.py @@ -43,7 +43,8 @@ def get_metadata(cls) -> Dict[str, Any]: """ metadata: Dict[str, Any] = {} metadata["icon"] = cls.ICON if cls.ICON else "Science" - + metadata["requires_download"] = bool(getattr(cls, "REQUIRES_DOWNLOAD", False)) + metadata["download_size_bytes"] = getattr(cls, "DOWNLOAD_SIZE_BYTES", None) return metadata @abstractmethod diff --git a/DashAI/back/models/base_torchvision_image_classifier.py b/DashAI/back/models/base_torchvision_image_classifier.py index be18391d7..700cd5f24 100644 --- a/DashAI/back/models/base_torchvision_image_classifier.py +++ b/DashAI/back/models/base_torchvision_image_classifier.py @@ -14,6 +14,7 @@ ) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.base_model import BaseModel +from DashAI.back.models.image_explainable_model import GradCamCompatibleModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -337,7 +338,7 @@ def __getitem__(self, idx): return _ImageDataset(x_dataset, y_dataset, image_size) -class TorchvisionImageClassifier(BaseModel, abc.ABC): +class TorchvisionImageClassifier(BaseModel, GradCamCompatibleModel, abc.ABC): """Abstract base for torchvision image classifiers. Subclasses must implement: @@ -412,6 +413,29 @@ def _freeze_backbone_params(self): for p in self._classifier_head().parameters(): p.requires_grad = True + def get_inference_transform(self): + """Return the transform applied to input images at inference time. + + Returns + ------- + Callable + Resize, tensor conversion and the ImageNet normalization used + by the training pipeline. + """ + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225], + ), + ] + ) + def prepare_output(self, dataset, is_fit=False): """Encode string labels to integer indices matching the model's class order.""" import pyarrow as pa diff --git a/DashAI/back/models/cnn_image_classifier.py b/DashAI/back/models/cnn_image_classifier.py index 606a5ec71..acbc81cdb 100644 --- a/DashAI/back/models/cnn_image_classifier.py +++ b/DashAI/back/models/cnn_image_classifier.py @@ -11,6 +11,7 @@ ) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.base_model import BaseModel +from DashAI.back.models.image_explainable_model import GradCamCompatibleModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -402,7 +403,7 @@ def forward(self, x): ) -class CNNImageClassifier(BaseModel): +class CNNImageClassifier(BaseModel, GradCamCompatibleModel): """CNN-based image classifier. A convolutional neural network with configurable depth and width that @@ -412,6 +413,7 @@ class CNNImageClassifier(BaseModel): SCHEMA = CNNImageClassifierSchema COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + DISPLAY_NAME: str = MultilingualString( en="CNN Image Classifier", es="Clasificador de Imágenes CNN", @@ -507,6 +509,25 @@ def _validate_architecture(self): f"for {self.num_conv_blocks} convolutional block(s)." ) + def get_inference_transform(self): + """Return the transform applied to input images at inference time. + + Returns + ------- + Callable + Resize and tensor conversion matching the training pipeline + (no normalization). + """ + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + ] + ) + def prepare_output(self, dataset, is_fit=False): """Encode string labels to integer indices matching the model's class order.""" import pyarrow as pa diff --git a/DashAI/back/models/efficientnet_b0_image_classifier.py b/DashAI/back/models/efficientnet_b0_image_classifier.py index a6be1bcb2..56517f400 100644 --- a/DashAI/back/models/efficientnet_b0_image_classifier.py +++ b/DashAI/back/models/efficientnet_b0_image_classifier.py @@ -1,13 +1,16 @@ """EfficientNet-B0 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class EfficientNetB0ImageClassifier(TorchvisionImageClassifier): +class EfficientNetB0ImageClassifier( + TorchvisionDownloadMixin, TorchvisionImageClassifier +): """EfficientNet-B0 image classifier (Tan & Le, 2019). Compact baseline of the EfficientNet family, which scales network width, @@ -53,13 +56,21 @@ class EfficientNetB0ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#00838F" ICON: str = "Speed" + DOWNLOAD_SIZE_BYTES: int = 21_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import EfficientNet_B0_Weights + + return EfficientNet_B0_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import EfficientNet_B0_Weights, efficientnet_b0 weights = EfficientNet_B0_Weights.DEFAULT if pretrained else None - model = efficientnet_b0(weights=weights) + with self.local_hub(): + model = efficientnet_b0(weights=weights) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/DashAI/back/models/hugging_face/albert_transformer.py b/DashAI/back/models/hugging_face/albert_transformer.py index a01622ab0..25b686d0a 100644 --- a/DashAI/back/models/hugging_face/albert_transformer.py +++ b/DashAI/back/models/hugging_face/albert_transformer.py @@ -34,29 +34,30 @@ class AlbertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Parameter efficient BERT variant for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Variante de BERT eficiente en parámetros para clasificación en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Variante do BERT eficiente em parâmetros para classificação de " "texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Parametereffiziente BERT-Variante für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "参数高效的 BERT 变体,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#00838F" ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "albert-base-v2" + DOWNLOAD_SIZE_BYTES: int = 96833451 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_albert" diff --git a/DashAI/back/models/hugging_face/base_opus_mt_transformer.py b/DashAI/back/models/hugging_face/base_opus_mt_transformer.py index 1b80e1269..a755cf80d 100644 --- a/DashAI/back/models/hugging_face/base_opus_mt_transformer.py +++ b/DashAI/back/models/hugging_face/base_opus_mt_transformer.py @@ -7,6 +7,7 @@ from sklearn.exceptions import NotFittedError +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin from DashAI.back.models.translation_model import TranslationModel from DashAI.back.models.utils import ( GPU_OR_CPU_PLACEHOLDER, @@ -17,7 +18,7 @@ from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset -class OpusMtTransformerMixin(TranslationModel): +class OpusMtTransformerMixin(HFDownloadableMixin, TranslationModel): """Shared implementation for Helsinki-NLP Opus-MT translation wrappers. Subclasses must define ``MODEL_NAME`` (the HuggingFace checkpoint ID) and @@ -29,20 +30,45 @@ class OpusMtTransformerMixin(TranslationModel): here so each language-pair subclass only needs to set class attributes. .. note:: - Requires internet access on first use to download pretrained weights - from the Hugging Face Hub. + For fresh training the pretrained weights must be downloaded first via + ``download()`` (this component requires a download). ``__init__`` with + no ``pretrained_dir`` loads the tokenizer and model from the + component's local download folder; it does not fetch from the Hugging + Face Hub. When loading a previously saved run, ``pretrained_dir`` is + set to the run directory so the tokenizer is read from there, making + trained runs self-contained. """ MODEL_NAME: str = "" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus_mt" + # Marian Opus-MT checkpoints are ~300 MB; declared statically for the UI. + DOWNLOAD_SIZE_BYTES: int = 300_000_000 - def __init__(self, model=None, **kwargs): + @classmethod + def hf_repos(cls): + """Derive the single HuggingFace repo from the subclass MODEL_NAME. + + Returns + ------- + list of tuple of (str, str) + A single ``(repo_id, repo_type)`` pair derived from ``MODEL_NAME``, + or an empty list when ``MODEL_NAME`` is not set. + """ + return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] + + def __init__(self, model=None, pretrained_dir: Optional[str] = None, **kwargs): """Initialize tokenizer and seq2seq model. Parameters ---------- model : transformers.PreTrainedModel or None Preloaded model to reuse instead of downloading weights. + pretrained_dir : str or None + Directory from which to load the tokenizer (and model weights when + ``model`` is ``None``). When ``None`` the component's download + folder is used, which is the correct path for fresh training. Pass + the run directory when restoring a saved run so the trained run + becomes self-contained and independent of the download folder. **kwargs Training hyperparameters forwarded to ``validate_and_transform``. """ @@ -56,7 +82,8 @@ def __init__(self, model=None, **kwargs): ) self.model_name = self.MODEL_NAME - self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) + source = pretrained_dir or str(self._repo_dir(self.MODEL_NAME)) + self.tokenizer = AutoTokenizer.from_pretrained(source) self.training_args = { "num_train_epochs": kwargs.get("num_train_epochs", 2), @@ -77,7 +104,7 @@ def __init__(self, model=None, **kwargs): if model is None: from transformers import AutoModelForSeq2SeqLM - self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model_name) + self.model = AutoModelForSeq2SeqLM.from_pretrained(source) else: self.model = model @@ -234,6 +261,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -256,6 +284,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/base_text_classification_transformer.py b/DashAI/back/models/hugging_face/base_text_classification_transformer.py index 741207eae..a2f9ffbf5 100644 --- a/DashAI/back/models/hugging_face/base_text_classification_transformer.py +++ b/DashAI/back/models/hugging_face/base_text_classification_transformer.py @@ -7,10 +7,11 @@ """ from pathlib import Path -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional, Union from sklearn.exceptions import NotFittedError +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin from DashAI.back.models.text_classification_model import TextClassificationModel from DashAI.back.models.utils import ( GPU_OR_CPU_PLACEHOLDER, @@ -22,7 +23,9 @@ from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset -class HuggingFaceTextClassificationTransformer(TextClassificationModel): +class HuggingFaceTextClassificationTransformer( + HFDownloadableMixin, TextClassificationModel +): """Base implementation for Hugging Face text classification wrappers. Subclasses are expected to define at least ``MODEL_NAME`` and optionally @@ -35,8 +38,8 @@ class HuggingFaceTextClassificationTransformer(TextClassificationModel): - Save/load utilities that preserve custom training parameters. .. note:: - Requires internet access on first use to download pretrained weights - from the Hugging Face Hub. + The pretrained weights must be downloaded from the Hugging Face Hub + (internet access required) before the model can be used. """ MODEL_NAME: str = "" @@ -44,8 +47,51 @@ class HuggingFaceTextClassificationTransformer(TextClassificationModel): "DashAI/back/user_models/temp_checkpoints_hf_text_classification" ) MAX_TOKEN_LENGTH: int = 512 + # Approximate on-disk size of the pretrained checkpoint; subclasses override + # with a value closer to their specific model for the download UI. + DOWNLOAD_SIZE_BYTES: int = 450_000_000 - def __init__(self, model=None, **kwargs): + @classmethod + def hf_repos(cls): + """Derive the single HuggingFace repo from the subclass ``MODEL_NAME``. + + Returns + ------- + list of tuple of (str, str) + A single ``(repo_id, repo_type)`` pair derived from ``MODEL_NAME``, + or an empty list when ``MODEL_NAME`` is not set. + """ + return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] + + def _pretrained_source(self, pretrained_dir: Optional[str]) -> str: + """Resolve where to load the tokenizer and weights from. + + Prefers an explicit ``pretrained_dir`` (a saved run), then the local + component download folder when the weights are present, and finally + falls back to the Hugging Face Hub repo id. Downloading is enforced by + the run/session gates before real use; the Hub fallback keeps direct + instantiation working when nothing has been downloaded. + + Parameters + ---------- + pretrained_dir : str or None + Directory of a previously saved run, if any. + + Returns + ------- + str + A path or repo id accepted by ``from_pretrained``. + """ + if pretrained_dir: + return pretrained_dir + try: + if self.is_downloaded(): + return str(self._repo_dir(self.MODEL_NAME)) + except Exception: + pass + return self.MODEL_NAME + + def __init__(self, model=None, pretrained_dir: Optional[str] = None, **kwargs): """Initialize the transformer model. The process includes the instantiation of the pretrained model and the @@ -77,7 +123,7 @@ def __init__(self, model=None, **kwargs): f"{self.__class__.__name__} must define a non-empty MODEL_NAME." ) - self.model_name = self.MODEL_NAME + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.log_train_every_n_epochs = kwargs.get("log_train_every_n_epochs", 1) @@ -375,6 +421,9 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + # Persist the tokenizer alongside the weights so a saved run is + # self-contained and does not depend on the component download folder. + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args_params.get("num_train_epochs"), @@ -419,6 +468,7 @@ def load( loaded_model = cls( model=model, + pretrained_dir=str(filename), num_labels=custom_params.get("num_labels"), num_train_epochs=custom_params.get("num_train_epochs", 2), batch_size=custom_params.get("batch_size", 16), diff --git a/DashAI/back/models/hugging_face/bert_transformer.py b/DashAI/back/models/hugging_face/bert_transformer.py index 7fddc261f..508448e3d 100644 --- a/DashAI/back/models/hugging_face/bert_transformer.py +++ b/DashAI/back/models/hugging_face/bert_transformer.py @@ -34,28 +34,29 @@ class BertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Bidirectional BERT model for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo BERT bidireccional para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo BERT bidirecional para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Bidirektionales BERT-Modell für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "双向 BERT 模型,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#1565C0" ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-uncased" + DOWNLOAD_SIZE_BYTES: int = 881643453 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bert" diff --git a/DashAI/back/models/hugging_face/bertin_transformer.py b/DashAI/back/models/hugging_face/bertin_transformer.py index 693004535..f3b24ced4 100644 --- a/DashAI/back/models/hugging_face/bertin_transformer.py +++ b/DashAI/back/models/hugging_face/bertin_transformer.py @@ -34,28 +34,29 @@ class BertinTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Spanish RoBERTa (BERTIN) pretrained on large Spanish corpora. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "RoBERTa en español (BERTIN) preentrenada en grandes corpus en español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "RoBERTa em espanhol (BERTIN) pré-treinada em grandes corpus em espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Spanisches RoBERTa (BERTIN) vortrainiert auf großen spanischen Korpora. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在大型西班牙语语料库上预训练的 RoBERTa(BERTIN)模型。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#AD1457" ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bertin-project/bertin-roberta-base-spanish" + DOWNLOAD_SIZE_BYTES: int = 1011598492 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bertin" diff --git a/DashAI/back/models/hugging_face/beto_transformer.py b/DashAI/back/models/hugging_face/beto_transformer.py index cddfb45a8..3e4ecb1bb 100644 --- a/DashAI/back/models/hugging_face/beto_transformer.py +++ b/DashAI/back/models/hugging_face/beto_transformer.py @@ -34,28 +34,29 @@ class BetoTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Spanish BERT (BETO) pretrained on Spanish corpora. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT en español (BETO) preentrenado en corpus en español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT em espanhol (BETO) pré-treinado em corpus em espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Spanisches BERT (BETO) vortrainiert auf spanischen Korpora. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在西班牙语语料库上预训练的 BERT(BETO)模型。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#C62828" ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "dccuchile/bert-base-spanish-wwm-cased" + DOWNLOAD_SIZE_BYTES: int = 440350800 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_beto" diff --git a/DashAI/back/models/hugging_face/deberta_v3_transformer.py b/DashAI/back/models/hugging_face/deberta_v3_transformer.py index da258deb9..03bd82c5a 100644 --- a/DashAI/back/models/hugging_face/deberta_v3_transformer.py +++ b/DashAI/back/models/hugging_face/deberta_v3_transformer.py @@ -61,4 +61,5 @@ class DebertaV3Transformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DebertaV3TransformerSchema MODEL_NAME: str = "microsoft/deberta-v3-base" + DOWNLOAD_SIZE_BYTES: int = 373616107 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_deberta_v3" diff --git a/DashAI/back/models/hugging_face/distilbert_transformer.py b/DashAI/back/models/hugging_face/distilbert_transformer.py index f43df4752..ebdff496d 100644 --- a/DashAI/back/models/hugging_face/distilbert_transformer.py +++ b/DashAI/back/models/hugging_face/distilbert_transformer.py @@ -305,4 +305,5 @@ class DistilBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "distilbert-base-uncased" + DOWNLOAD_SIZE_BYTES: int = 536641210 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_distilbert" diff --git a/DashAI/back/models/hugging_face/electra_transformer.py b/DashAI/back/models/hugging_face/electra_transformer.py index 7371a6f2d..43e8e65dc 100644 --- a/DashAI/back/models/hugging_face/electra_transformer.py +++ b/DashAI/back/models/hugging_face/electra_transformer.py @@ -34,28 +34,29 @@ class ElectraTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Sample efficient ELECTRA discriminator for text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Discriminador ELECTRA eficiente en muestras para clasificación de texto. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Discriminador ELECTRA eficiente em amostras para classificação de texto. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Stichprobeneffizienter ELECTRA-Diskriminator für Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "样本高效的 ELECTRA 判别器,用于文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#558B2F" ICON: str = "ElectricBolt" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "google/electra-small-discriminator" + DOWNLOAD_SIZE_BYTES: int = 54946248 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_electra" diff --git a/DashAI/back/models/hugging_face/gguf_text_generation_base.py b/DashAI/back/models/hugging_face/gguf_text_generation_base.py new file mode 100644 index 000000000..189f15629 --- /dev/null +++ b/DashAI/back/models/hugging_face/gguf_text_generation_base.py @@ -0,0 +1,389 @@ +"""Shared base for GGUF-backed text-generation models loaded via llama.cpp.""" + +from typing import List, Optional, Union + +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.text_to_text_generation_model import ( + TextToTextGenerationTaskModel, +) +from DashAI.back.models.utils import ( + LLAMA_DEVICE_ENUM, + LLAMA_DEVICE_PLACEHOLDER, + LLAMA_DEVICE_TO_IDX, +) + + +class GGUFTextGenerationSchema(BaseSchema): + """Schema for GGUF-based text-generation model hyperparameters. + + All GGUF checkpoint subclasses share this schema. The schema controls + generation length, sampling randomness, repetition penalty, context budget, + and the hardware device used by llama.cpp. + """ + + max_tokens: schema_field( + int_field(ge=1), + placeholder=100, + description=MultilingualString( + en=( + "Maximum number of new tokens the model will generate per response. " + "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " + "answers, 500-1000 for detailed explanations or code. Must not " + "exceed the context window minus the prompt length." + ), + es=( + "Número máximo de tokens nuevos que el modelo generará por respuesta. " + "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " + "para respuestas cortas, 500-1000 para explicaciones detalladas o " + "código. No debe superar la ventana de contexto menos la longitud " + "del prompt." + ), + pt=( + "Número máximo de tokens novos que o modelo gerará por resposta. " + "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " + "para respostas curtas, 500-1000 para explicações detalhadas ou " + "código. Não deve exceder a janela de contexto menos o comprimento " + "do prompt." + ), + de=( + "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " + "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " + "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " + "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " + "überschreiten." + ), + zh=( + "模型每次响应生成的最大新 token 数量。" + "大约 1 token 约等于 0.75 个英文单词。短答案设为 100-200," + "详细说明或代码设为 500-1000。不得超过上下文窗口减去提示词长度的值。" + ), + ), + alias=MultilingualString( + en="Max tokens", + es="Tokens máximos", + pt="Tokens máximos", + de="Maximale neue Token", + zh="最大 token 数", + ), + ) # type: ignore + + temperature: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.7, + description=MultilingualString( + en=( + "Sampling temperature controlling output randomness (range 0.0-1.0). " + "At 0.0 the model always picks the most likely token (greedy, fully " + "deterministic). Around 0.7 is a good balance for conversational " + "tasks. At 1.0 outputs are maximally varied and unpredictable." + ), + es=( + "Temperatura de muestreo que controla la aleatoriedad de la salida " + "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " + "probable (greedy, totalmente determinista). Alrededor de 0.7 es " + "un buen equilibrio para tareas conversacionales. En 1.0 las " + "salidas son máximamente variadas e impredecibles." + ), + pt=( + "Temperatura de amostragem que controla a aleatoriedade da saída " + "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " + "provável (greedy, totalmente determinístico). Em torno de 0.7 é " + "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " + "saídas são maximamente variadas e imprevisíveis." + ), + de=( + "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." + "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " + "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " + "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " + "unvorhersehbar." + ), + zh=( + "控制输出随机性的采样温度(范围 0.0-1.0)。" + "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" + "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" + ), + ), + alias=MultilingualString( + en="Temperature", + es="Temperatura", + pt="Temperatura", + de="Temperatur", + zh="温度", + ), + ) # type: ignore + + frequency_penalty: schema_field( + float_field(ge=0.0, le=2.0), + placeholder=0.1, + description=MultilingualString( + en=( + "Penalizes tokens that have already appeared in the output based on " + "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " + "and the model may repeat itself. Values around 0.1-0.3 gently " + "discourage repetition. High values (1.5+) strongly prevent reuse " + "of any word, which may produce less coherent text." + ), + es=( + "Penaliza los tokens que ya aparecieron en la salida según su " + "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " + "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " + "suavemente la repetición. Valores altos (1.5+) previenen " + "fuertemente la reutilización de palabras, lo que puede producir " + "texto menos coherente." + ), + pt=( + "Penaliza os tokens que já apareceram na saída com base em " + "sua frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o " + "modelo pode se repetir. Valores em torno de 0.1-0.3 desestimulam " + "suavemente a repetição. Valores altos (1.5+) impedem fortemente a " + "reutilização de palavras, o que pode produzir texto menos coerente." + ), + de=( + "Bestraft Token, die bereits in der Ausgabe erschienen sind, " + "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " + "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " + "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " + "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " + "führen kann." + ), + zh=( + "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" + "0.0 时无惩罚,模型可能重复输出。0.1-0.3 左右可轻微抑制重复。" + "高值(1.5+)会强烈阻止任何词的复用,可能导致文本连贯性下降。" + ), + ), + alias=MultilingualString( + en="Frequency penalty", + es="Penalización de frecuencia", + pt="Penalização de frequência", + de="Häufigkeitsstrafe", + zh="频率惩罚", + ), + ) # type: ignore + + context_window: schema_field( + int_field(ge=1, le=131072), + placeholder=512, + description=MultilingualString( + en=( + "Total token budget for a single forward pass, including both the " + "input prompt and the generated response. Larger values allow longer " + "conversations but consume more RAM/VRAM. Llama 3.1 supports up to " + "128K tokens natively; Llama 3.2 models support up to 128K tokens." + ), + es=( + "Presupuesto total de tokens para una sola pasada, incluyendo tanto " + "el prompt de entrada como la respuesta generada. Valores más altos " + "permiten conversaciones más largas pero consumen más RAM/VRAM. " + "Llama 3.1 soporta hasta 128K tokens de forma nativa; los modelos " + "Llama 3.2 soportan hasta 128K tokens." + ), + pt=( + "Orçamento total de tokens para uma única passagem, incluindo tanto " + "o prompt de entrada quanto a resposta gerada. Valores maiores " + "permitem conversas mais longas mas consomem mais RAM/VRAM. " + "Llama 3.1 suporta até 128K tokens nativamente; os modelos " + "Llama 3.2 suportam até 128K tokens." + ), + de=( + "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " + "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " + "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " + "Llama 3.1 unterstützt nativ bis zu 128K Token; Llama-3.2-Modelle " + "unterstützen ebenfalls bis zu 128K Token." + ), + zh=( + "单次前向传播的总 token 预算,包含输入提示和生成响应。" + "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" + "Llama 3.1 原生支持最多 128K token;" + "Llama 3.2 模型同样支持最多 128K token。" + ), + ), + alias=MultilingualString( + en="Context window", + es="Ventana de contexto", + pt="Janela de contexto", + de="Kontextfenster", + zh="上下文窗口", + ), + ) # type: ignore + + device: schema_field( + enum_field(enum=LLAMA_DEVICE_ENUM), + placeholder=LLAMA_DEVICE_PLACEHOLDER, + description=MultilingualString( + en=( + "Hardware device for llama.cpp inference. 'CPU' runs the model " + "fully in RAM with no GPU requirement. Selecting a GPU option " + "offloads all layers for faster inference, setting n_gpu_layers=-1 " + "so every transformer layer is GPU-accelerated." + ), + es=( + "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " + "ejecuta el modelo completamente en RAM sin requisito de GPU. " + "Seleccionar una opción de GPU descarga todas las capas para " + "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " + "cada capa del transformer sea acelerada por GPU." + ), + pt=( + "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " + "executa o modelo completamente na RAM sem requisito de GPU. " + "Selecionar uma opção de GPU descarrega todas as camadas para " + "inferência mais rápida, definindo n_gpu_layers=-1 para que " + "cada camada do transformer seja acelerada por GPU." + ), + de=( + "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " + "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " + "lagert alle Schichten für schnellere Inferenz aus und setzt " + "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." + ), + zh=( + "llama.cpp 推理所使用的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" + "选择 GPU 选项会将所有层卸载以加快推理速度," + "并设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" + ), + ), + alias=MultilingualString( + en="Device", + es="Dispositivo", + pt="Dispositivo", + de="Gerät", + zh="设备", + ), + ) # type: ignore + + +class GGUFTextGenerationModel(HFDownloadableMixin, TextToTextGenerationTaskModel): + """Base class for GGUF quantized text-generation models loaded via llama.cpp. + + Each concrete subclass represents one specific checkpoint and sets the class + attributes ``REPO_ID``, ``GGUF_PATTERN``, and ``DOWNLOAD_SIZE_BYTES``. The + base class provides the shared ``hf_repos`` classmethod, a helper that locates + the downloaded GGUF file on disk, the ``__init__`` that loads the model, and + the ``generate`` method. + + Subclasses must NOT override ``hf_repos`` or ``_local_gguf_path`` unless they + need non-standard repo layout. + """ + + REPO_ID: str = "" + GGUF_PATTERN: str = "" + DOWNLOAD_SIZE_BYTES: Optional[int] = None + COMPATIBLE_COMPONENTS = ["TextToTextGenerationTask"] + SCHEMA = GGUFTextGenerationSchema + + @classmethod + def hf_repos( + cls, + ) -> List[Union[tuple, tuple]]: + """Return the single HuggingFace repo entry for this checkpoint. + + Returns + ------- + list of tuple + A list containing one 3-tuple ``(repo_id, "model", [gguf_pattern])`` + when ``REPO_ID`` is set, or an empty list otherwise. + """ + if cls.REPO_ID: + return [(cls.REPO_ID, "model", [cls.GGUF_PATTERN])] + return [] + + @classmethod + def _local_gguf_path(cls): + """Locate the downloaded GGUF file within this component's repo directory. + + Returns + ------- + pathlib.Path + Absolute path to the first ``*.gguf`` file found under the repo + directory for ``REPO_ID``. + + Raises + ------ + StopIteration + If no ``*.gguf`` file exists inside the repo directory. + """ + return next(iter(cls._repo_dir(cls.REPO_ID).glob("*.gguf"))) + + def __init__(self, **kwargs): + """Load a GGUF checkpoint from disk and initialise the llama.cpp model. + + Parameters + ---------- + **kwargs : dict + max_tokens : int, optional + Maximum number of new tokens to generate per response. Default 100. + temperature : float, optional + Sampling temperature in [0.0, 1.0]. Default 0.7. + frequency_penalty : float, optional + Token-frequency penalty in [0.0, 2.0]. Default 0.1. + context_window : int, optional + Total token budget for a single forward pass. Default 512. + device : str, optional + Target device from ``LLAMA_DEVICE_ENUM``. CPU runs in RAM only; + a GPU label enables full GPU offload via ``n_gpu_layers=-1``. + + Raises + ------ + RuntimeError + If ``llama-cpp-python`` is not installed. + """ + try: + from llama_cpp import Llama + except ImportError as e: + raise RuntimeError( + "llama-cpp-python is not installed. " + "Please install it to use this model." + ) from e + + kwargs = self.validate_and_transform(kwargs) + self.max_tokens = kwargs.pop("max_tokens", 100) + self.temperature = kwargs.pop("temperature", 0.7) + self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) + self.n_ctx = kwargs.pop("context_window", 512) + + device_val = kwargs.get("device") + use_gpu = LLAMA_DEVICE_TO_IDX.get(device_val, -1) >= 0 + main_gpu = LLAMA_DEVICE_TO_IDX.get(device_val, 0) if use_gpu else 0 + + self.model = Llama( + model_path=str(self._local_gguf_path()), + verbose=True, + n_ctx=self.n_ctx, + n_gpu_layers=-1 if use_gpu else 0, + main_gpu=main_gpu, + ) + + def generate(self, prompt: list) -> List[str]: + """Generate a reply for the given chat prompt. + + Parameters + ---------- + prompt : list of dict + Conversation history in OpenAI chat format. Each dict must contain + at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) + and ``"content"`` (the message text). + + Returns + ------- + list of str + A single-element list containing the model's reply text, extracted + from ``choices[0]["message"]["content"]``. + """ + output = self.model.create_chat_completion( + messages=prompt, + max_tokens=self.max_tokens, + temperature=self.temperature, + frequency_penalty=self.frequency_penalty, + ) + return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/llama_model.py b/DashAI/back/models/hugging_face/llama_model.py index 18f36239b..fd59f4c7c 100644 --- a/DashAI/back/models/hugging_face/llama_model.py +++ b/DashAI/back/models/hugging_face/llama_model.py @@ -1,485 +1,186 @@ -from typing import List +"""Llama 3.x Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -LLAMA_FILENAME_MAP = { - "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": "*Q4_K_M.gguf", - "bartowski/Llama-3.2-1B-Instruct-GGUF": "*Q4_K_M.gguf", - "bartowski/Llama-3.2-3B-Instruct-GGUF": "*Q4_K_M.gguf", -} +class Llama31_8BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.1 8B Instruct GGUF checkpoint (Q4_K_M quantization). -class LlamaSchema(BaseSchema): - """Configuration schema for Meta Llama 3.x text generation. + An 8B-parameter instruction-tuned model from Meta with strong general + reasoning and multilingual ability. Weights are stored locally after a + one-time download from HuggingFace. - Configures the GGUF checkpoint variant (``model_name``), generation - behaviour (``max_tokens``, ``temperature``, ``frequency_penalty``), - context length (``context_window``), device target (``device``), and - system prompt (``system_prompt``) for ``LlamaModel``. + References + ---------- + - https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF """ - model_name: schema_field( - enum_field( - enum=[ - "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF", - "bartowski/Llama-3.2-1B-Instruct-GGUF", - "bartowski/Llama-3.2-3B-Instruct-GGUF", - ] + REPO_ID = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 4920739232 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#1a237e" + DISPLAY_NAME = MultilingualString( + en="Llama 3.1 8B Instruct", + es="Llama 3.1 8B Instruct", + pt="Llama 3.1 8B Instruct", + de="Llama 3.1 8B Instruct", + zh="Llama 3.1 8B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Meta Llama 3.1 8B Instruct is an 8B-parameter instruction-tuned language " + "model, loaded as a Q4_K_M GGUF for efficient CPU or GPU inference. It " + "offers strong reasoning, coding, and multilingual capabilities. This is " + "the largest text-generation model in DashAI and benefits from a GPU. " + "Model available at " + "https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF." ), - placeholder="bartowski/Llama-3.2-3B-Instruct-GGUF", - description=MultilingualString( - en=( - "The Meta Llama 3.x Instruct checkpoint to load in GGUF format via " - "bartowski's community quantizations. 'Llama-3.2-1B' (~1B parameters) " - "is the smallest and fastest, ideal for CPU-only systems. " - "'Llama-3.2-3B' (~3B parameters) offers a good speed/quality " - "trade-off. " - "'Meta-Llama-3.1-8B' (~8B parameters) delivers the highest quality " - "at the cost of more RAM and slower inference." - ), - es=( - "El checkpoint Meta Llama 3.x Instruct a cargar en formato GGUF " - "mediante las cuantizaciones comunitarias de bartowski. " - "'Llama-3.2-1B' (~1B parámetros) es el más pequeño y rápido, " - "ideal para sistemas solo con CPU. " - "'Llama-3.2-3B' (~3B parámetros) ofrece un buen equilibrio entre " - "velocidad y calidad. 'Meta-Llama-3.1-8B' (~8B parámetros) entrega " - "la mayor calidad a costa de más RAM e inferencia más lenta." - ), - pt=( - "O checkpoint Meta Llama 3.x Instruct para carregar em formato GGUF " - "via quantizações comunitárias de bartowski. " - "'Llama-3.2-1B' (~1B parâmetros) é o menor e mais rápido, " - "ideal para sistemas apenas com CPU. " - "'Llama-3.2-3B' (~3B parâmetros) oferece um bom equilíbrio entre " - "velocidade e qualidade. 'Meta-Llama-3.1-8B' (~8B parâmetros) " - "entrega a maior qualidade ao custo de mais RAM e " - "inferência mais lenta." - ), - de=( - "Der im GGUF-Format zu ladende Meta Llama 3.x Instruct-Checkpoint " - "über bartowskis Community-Quantisierungen. " - "'Llama-3.2-1B' (~1B Parameter) ist der kleinste und schnellste, " - "ideal für reine CPU-Systeme. " - "'Llama-3.2-3B' (~3B Parameter) bietet ein gutes Geschwindigkeit-" - "Qualitäts-Verhältnis. 'Meta-Llama-3.1-8B' (~8B Parameter) liefert " - "die höchste Qualität auf Kosten von mehr RAM und langsamerer Inferenz." - ), - zh=( - "通过 bartowski 社区量化加载的 Meta Llama 3.x Instruct GGUF 检查点。" - "'Llama-3.2-1B'(约 1B 参数)是最小最快的版本,适合仅使用 CPU 的系统。" - "'Llama-3.2-3B'(约 3B 参数)在速度与质量之间取得良好平衡。" - "'Meta-Llama-3.1-8B'(约 8B 参数)质量最高," - "但需要更多内存且推理速度较慢。" - ), + es=( + "Meta Llama 3.1 8B Instruct es un modelo de lenguaje de 8B parametros " + "ajustado para instrucciones, cargado como GGUF Q4_K_M para inferencia " + "eficiente en CPU o GPU. Ofrece solida capacidad de razonamiento, " + "programacion y multilingue. Es el modelo de generacion de texto mas " + "grande de DashAI y se beneficia de una GPU." ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", + pt=( + "Meta Llama 3.1 8B Instruct e um modelo de linguagem de 8B parametros " + "ajustado para instrucoes, carregado como GGUF Q4_K_M para inferencia " + "eficiente em CPU ou GPU. Oferece solida capacidade de raciocinio, " + "programacao e multilingue. E o maior modelo de geracao de texto do " + "DashAI e se beneficia de uma GPU." ), - ) # type: ignore - - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code. Must not " - "exceed the context window minus the prompt length." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas o " - "código. No debe superar la ventana de contexto menos la longitud " - "del prompt." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas ou " - "código. Não deve exceder a janela de contexto menos o comprimento " - "do prompt." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " - "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " - "überschreiten." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "约 1 token ≈ 0.75 个英文单词。简短回答设置 100-200," - "详细说明或代码设置 500-1000。不得超过上下文窗口减去提示词长度的值。" - ), + de=( + "Meta Llama 3.1 8B Instruct ist ein 8B-Parameter-Instruktionsmodell, " + "als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz geladen. Es " + "bietet starke Faehigkeiten in Schlussfolgern, Programmierung und " + "Mehrsprachigkeit. Es ist das groesste Textgenerierungsmodell in DashAI " + "und profitiert von einer GPU." ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", + zh=( + "Meta Llama 3.1 8B Instruct 是 80 亿参数的指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "它具备强大的推理、编程和多语言能力。" + "这是 DashAI 中最大的文本生成模型,使用 GPU 效果更佳。" ), - ) # type: ignore + ) - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model always picks the most likely token (greedy, fully " - "deterministic). Around 0.7 is a good balance for conversational " - "tasks. At 1.0 outputs are maximally varied and unpredictable." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad de la salida " - "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " - "probable (greedy, totalmente determinista). Alrededor de 0.7 es " - "un buen equilibrio para tareas conversacionales. En 1.0 las " - "salidas son máximamente variadas e impredecibles." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " - "provável (greedy, totalmente determinístico). Em torno de 0.7 é " - "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " - "saídas são maximamente variadas e imprevisíveis." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " - "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " - "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " - "unvorhersehbar." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" - "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " - "and the model may repeat itself. Values around 0.1-0.3 gently " - "discourage repetition. High values (1.5+) strongly prevent reuse " - "of any word, which may produce less coherent text." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " - "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " - "suavemente la repetición. Valores altos (1.5+) previenen " - "fuertemente la reutilización de palabras, lo que puede producir " - "texto menos coherente." - ), - pt=( - "Penaliza tokens que já apareceram na saída com base em sua " - "frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o modelo " - "pode se repetir. Valores em torno de 0.1-0.3 desencorajam " - "suavemente a repetição. Valores altos (1.5+) impedem fortemente " - "o reuso de palavras, o que pode produzir texto menos coerente." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " - "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " - "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " - "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " - "führen kann." - ), - zh=( - "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "0.0 时无惩罚,模型可能重复自身。0.1-0.3 左右的值可温和抑制重复。" - "高值(1.5+)会强烈阻止任何词的重复使用,可能导致文本连贯性下降。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalidade de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", - ), - ) # type: ignore +class Llama32_1BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.2 1B Instruct GGUF checkpoint (Q4_K_M quantization). + + A lightweight 1B-parameter instruction-tuned model from Meta suitable for + CPU inference. Weights are stored locally after a one-time download from + HuggingFace. + + References + ---------- + - https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF + """ - context_window: schema_field( - int_field(ge=1, le=131072), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Larger values allow longer " - "conversations but consume more RAM/VRAM. Llama 3.1 supports up to " - "128K tokens natively; Llama 3.2 models support up to 128K tokens." - ), - es=( - "Presupuesto total de tokens para una sola pasada, incluyendo tanto " - "el prompt de entrada como la respuesta generada. Valores más altos " - "permiten conversaciones más largas pero consumen más RAM/VRAM. " - "Llama 3.1 soporta hasta 128K tokens de forma nativa; los modelos " - "Llama 3.2 soportan hasta 128K tokens." - ), - pt=( - "Orçamento total de tokens para uma única passagem, incluindo tanto " - "o prompt de entrada quanto a resposta gerada. Valores maiores " - "permitem conversas mais longas mas consomem mais RAM/VRAM. " - "Llama 3.1 suporta até 128K tokens nativamente; os modelos " - "Llama 3.2 suportam até 128K tokens." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " - "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " - "Llama 3.1 unterstützt nativ bis zu 128K Token; Llama-3.2-Modelle " - "unterstützen ebenfalls bis zu 128K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示和生成响应。" - "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" - "Llama 3.1 原生支持最多 128K token;" - "Llama 3.2 模型同样支持最多 128K token。" - ), + REPO_ID = "bartowski/Llama-3.2-1B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 807694464 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#1a237e" + DISPLAY_NAME = MultilingualString( + en="Llama 3.2 1B Instruct", + es="Llama 3.2 1B Instruct", + pt="Llama 3.2 1B Instruct", + de="Llama 3.2 1B Instruct", + zh="Llama 3.2 1B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Meta Llama 3.2 1B Instruct is a lightweight 1B-parameter " + "instruction-tuned language model, loaded as a Q4_K_M GGUF for fast CPU " + "inference. It is a good balance of speed and quality for everyday tasks. " + "Model available at " + "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + es=( + "Meta Llama 3.2 1B Instruct es un modelo de lenguaje ligero de 1B " + "parametros ajustado para instrucciones, cargado como GGUF Q4_K_M para " + "inferencia rapida en CPU. Ofrece un buen equilibrio entre velocidad y " + "calidad para tareas cotidianas." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. Selecting a GPU option " - "offloads all layers for faster inference, setting n_gpu_layers=-1 " - "so every transformer layer is GPU-accelerated." - ), - es=( - "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " - "ejecuta el modelo completamente en RAM sin requisito de GPU. " - "Seleccionar una opción de GPU descarga todas las capas para " - "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " - "cada capa del transformer sea acelerada por GPU." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " - "executa o modelo completamente em RAM sem requisito de GPU. " - "Selecionar uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida, definindo n_gpu_layers=-1 para que " - "cada camada do transformer seja acelerada por GPU." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " - "lagert alle Schichten für schnellere Inferenz aus und setzt " - "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." - ), - zh=( - "llama.cpp 推理的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" - "选择 GPU 选项可卸载所有层以加快推理速度," - "设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" - ), + pt=( + "Meta Llama 3.2 1B Instruct e um modelo de linguagem leve de 1B " + "parametros ajustado para instrucoes, carregado como GGUF Q4_K_M para " + "inferencia rapida em CPU. Oferece um bom equilibrio entre velocidade e " + "qualidade para tarefas cotidianas." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + de=( + "Meta Llama 3.2 1B Instruct ist ein leichtes 1B-Parameter-" + "Instruktionsmodell, als Q4_K_M-GGUF fuer schnelle CPU-Inferenz geladen. " + "Es bietet ein gutes Gleichgewicht aus Geschwindigkeit und Qualitaet fuer " + "alltaegliche Aufgaben." ), - ) # type: ignore - + zh=( + "Meta Llama 3.2 1B Instruct 是轻量级的 10 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持快速 CPU 推理。" + "在日常任务中兼顾速度与质量。" + ), + ) -class LlamaModel(TextToTextGenerationTaskModel): - """Meta Llama 3.x instruction-tuned model for text generation via llama.cpp. - Wraps the Meta Llama 3.x family of open-weight instruction-tuned LLMs - loaded in Q4_K_M GGUF format using the ``llama-cpp-python`` library. - GGUF quantization enables efficient CPU and GPU inference without requiring - full-precision weights, making the models practical on consumer hardware. +class Llama32_3BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.2 3B Instruct GGUF checkpoint (Q4_K_M quantization). - Three sizes are available via bartowski's community quantizations: - 1B (fastest, CPU-friendly), 3B (balanced), and 8B (highest quality). + A 3B-parameter instruction-tuned model from Meta offering higher quality + than the 1B variant while remaining CPU-friendly. Weights are stored locally + after a one-time download from HuggingFace. References ---------- - - [1] Meta AI, "Llama 3", 2024. https://ai.meta.com/blog/meta-llama-3/ - - [2] https://huggingface.co/bartowski + - https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF """ - SCHEMA = LlamaSchema + REPO_ID = "bartowski/Llama-3.2-3B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 2019377696 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#1a237e" - DISPLAY_NAME: str = MultilingualString( - en="Llama Model", - es="Modelo Llama", - pt="Modelo Llama", - de="Llama-Modell", - zh="Llama 模型", + DISPLAY_NAME = MultilingualString( + en="Llama 3.2 3B Instruct", + es="Llama 3.2 3B Instruct", + pt="Llama 3.2 3B Instruct", + de="Llama 3.2 3B Instruct", + zh="Llama 3.2 3B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Meta Llama 3.x is a family of open instruction-tuned large language " - "models developed by Meta AI, loaded in GGUF format for efficient CPU " - "and GPU inference via the llama.cpp library. It supports multi-turn " - "conversation, reasoning, coding, and general text generation. Available " - "in 1B, 3B, and 8B parameter sizes. Models are hosted at " - "https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct is a 3B-parameter instruction-tuned language " + "model, loaded as a Q4_K_M GGUF for efficient CPU or GPU inference. It " + "offers stronger reasoning and generation quality than the 1B variant. " + "Model available at " + "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF." ), es=( - "Meta Llama 3.x es una familia de modelos de lenguaje grande de código " - "abierto ajustados para instrucciones, desarrollados por Meta AI, cargados " - "en formato GGUF para inferencia eficiente en CPU y GPU mediante la " - "librería llama.cpp. Soporta conversación multi-turno, razonamiento, " - "programación y generación de texto en general. Disponible en tamaños de " - "1B, 3B y 8B parámetros. Los modelos están en " - "https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct es un modelo de lenguaje de 3B parametros " + "ajustado para instrucciones, cargado como GGUF Q4_K_M para inferencia " + "eficiente en CPU o GPU. Ofrece mejor razonamiento y calidad de " + "generacion que la variante de 1B." ), pt=( - "Meta Llama 3.x é uma família de modelos de linguagem grande de código " - "aberto ajustados para instruções, desenvolvidos pela Meta AI, carregados " - "em formato GGUF para inferência eficiente em CPU e GPU via a biblioteca " - "llama.cpp. Suporta conversação multi-turno, raciocínio, programação e " - "geração de texto em geral. Disponível nos tamanhos de parâmetros 1B, 3B " - "e 8B. Os modelos estão em https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct e um modelo de linguagem de 3B parametros " + "ajustado para instrucoes, carregado como GGUF Q4_K_M para inferencia " + "eficiente em CPU ou GPU. Oferece melhor raciocinio e qualidade de " + "geracao do que a variante de 1B." ), de=( - "Meta Llama 3.x ist eine Familie offener instruktionsoptimierter großer " - "Sprachmodelle von Meta AI, im GGUF-Format für effiziente CPU- und " - "GPU-Inferenz über die llama.cpp-Bibliothek geladen. Unterstützt " - "Mehrfachdialog, Schlussfolgerung, Programmierung und allgemeine " - "Textgenerierung. Verfügbar in den Parametergrößen 1B, 3B und 8B. " - "Modelle unter https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct ist ein 3B-Parameter-Instruktionsmodell, " + "als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz geladen. Es " + "bietet besseres Schlussfolgern und Generierungsqualitaet als die " + "1B-Variante." ), zh=( - "Meta Llama 3.x 是 Meta AI 开发的开放指令微调大语言模型系列," - "以 GGUF 格式加载,通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理、编程和通用文本生成。提供 1B、3B 和 8B 参数规格。" - "模型托管于 https://huggingface.co/bartowski。" + "Meta Llama 3.2 3B Instruct 是 30 亿参数的指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "与 1B 变体相比,它具有更强的推理能力和生成质量。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Llama 3.x GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"bartowski/Llama-3.2-3B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "bartowski/Llama-3.2-3B-Instruct-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = LLAMA_FILENAME_MAP.get(self.model_name, "*Q4_K_M.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/m2m100_transformer.py b/DashAI/back/models/hugging_face/m2m100_transformer.py index 264e0a49f..de5c618cb 100644 --- a/DashAI/back/models/hugging_face/m2m100_transformer.py +++ b/DashAI/back/models/hugging_face/m2m100_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import schema_field, string_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -97,7 +100,7 @@ class M2M100TransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class M2M100Transformer(TranslationModel): +class M2M100Transformer(HFPretrainedDownloadMixin, TranslationModel): """M2M100 multilingual seq2seq model for configurable language-pair translation. Fine-tunes the ``facebook/m2m100_418M`` checkpoint from Meta AI. The base @@ -128,39 +131,41 @@ class M2M100Transformer(TranslationModel): en=( "Facebook M2M-100 model for direct translation across 100 languages " "using ISO 639-1 codes. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo M2M-100 de Facebook para traducción directa entre 100 idiomas " "usando códigos ISO 639-1. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo M2M-100 do Facebook para tradução direta entre 100 idiomas " "usando códigos ISO 639-1. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Facebook M2M-100-Modell für direkte Übersetzung zwischen 100 Sprachen " "mit ISO 639-1-Codes. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "Facebook M2M-100 模型,使用 ISO 639-1 代码支持" " 100 种语言之间的直接翻译。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#6A1B9A" ICON: str = "Language" + MODEL_NAME: str = "facebook/m2m100_418M" + DOWNLOAD_SIZE_BYTES: int = 1941936305 - def __init__(self, model=None, **kwargs): + def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) from transformers import AutoTokenizer - self.model_name = "facebook/m2m100_418M" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.source_language = kwargs.get("source_language", "en") @@ -352,6 +357,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -376,6 +382,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/minilm_transformer.py b/DashAI/back/models/hugging_face/minilm_transformer.py index 6d8e75a87..c9990e4e5 100644 --- a/DashAI/back/models/hugging_face/minilm_transformer.py +++ b/DashAI/back/models/hugging_face/minilm_transformer.py @@ -34,28 +34,29 @@ class MiniLMTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Compact, fast MiniLM model for efficient text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo MiniLM compacto y rápido para clasificación de texto eficiente. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo MiniLM compacto e rápido para classificação de texto eficiente. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Kompaktes, schnelles MiniLM-Modell für effiziente Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "紧凑快速的 MiniLM 模型,用于高效文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#0277BD" ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "microsoft/MiniLM-L12-H384-uncased" + DOWNLOAD_SIZE_BYTES: int = 133721893 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_minilm" diff --git a/DashAI/back/models/hugging_face/mistral_model.py b/DashAI/back/models/hugging_face/mistral_model.py index ab0f450bf..52c38adea 100644 --- a/DashAI/back/models/hugging_face/mistral_model.py +++ b/DashAI/back/models/hugging_face/mistral_model.py @@ -1,441 +1,124 @@ -from typing import List +"""Mistral Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -class MistralSchema(BaseSchema): - """Schema for MistralModel hyperparameters. - - Configures the checkpoint variant, generation length, sampling temperature, - frequency penalty, context window, and target device for Mistral Instruct - models loaded via ``llama-cpp-python`` in GGUF format. - """ - - model_name: schema_field( - enum_field( - enum=[ - "bartowski/Mistral-7B-Instruct-v0.3-GGUF", - "bartowski/Mistral-Nemo-Instruct-2407-GGUF", - ] - ), - placeholder="bartowski/Mistral-7B-Instruct-v0.3-GGUF", - description=MultilingualString( - en=( - "The Mistral Instruct checkpoint to load in GGUF format. " - "'Mistral-7B-Instruct-v0.3' is a 7B-parameter instruction model " - "that delivers strong performance for its size. " - "'Mistral-Nemo-Instruct-2407' is a 12B-parameter model jointly " - "developed with NVIDIA, featuring a 128K context window and " - "improved multilingual capabilities." - ), - es=( - "El checkpoint Mistral Instruct a cargar en formato GGUF. " - "'Mistral-7B-Instruct-v0.3' es un modelo de instrucción de 7B " - "parámetros con fuerte rendimiento para su tamaño. " - "'Mistral-Nemo-Instruct-2407' es un modelo de 12B parámetros " - "desarrollado conjuntamente con NVIDIA, con una ventana de contexto " - "de 128K y mejores capacidades multilingües." - ), - pt=( - "O checkpoint Mistral Instruct para carregar em formato GGUF. " - "'Mistral-7B-Instruct-v0.3' é um modelo de instrução de 7B " - "parâmetros com forte desempenho para seu tamanho. " - "'Mistral-Nemo-Instruct-2407' é um modelo de 12B parâmetros " - "desenvolvido conjuntamente com a NVIDIA, com uma janela de contexto " - "de 128K e melhores capacidades multilíngues." - ), - de=( - "Der im GGUF-Format zu ladende Mistral Instruct-Checkpoint. " - "'Mistral-7B-Instruct-v0.3' ist ein 7B-Parameter-Instruktionsmodell " - "mit starker Leistung für seine Größe. " - "'Mistral-Nemo-Instruct-2407' ist ein 12B-Parameter-Modell, gemeinsam " - "mit NVIDIA entwickelt, mit einem 128K-Kontextfenster und verbesserten " - "mehrsprachigen Fähigkeiten." - ), - zh=( - "要加载的 Mistral Instruct 检查点(GGUF 格式)。" - "'Mistral-7B-Instruct-v0.3' 是 7B 参数指令模型,性能出色。" - "'Mistral-Nemo-Instruct-2407' 是与 NVIDIA 联合开发的 12B 参数模型," - "支持 128K 上下文窗口,多语言能力更强。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Mistral7BInstructV03(GGUFTextGenerationModel): + """Mistral 7B Instruct v0.3 GGUF checkpoint (Q4_K_M quantization). - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas " - "o código." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas " - "ou código." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "大约 1 token 约等于 0.75 个英文单词。" - "短回答设为 100-200,详细解释或代码设为 500-1000。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore + A 7B-parameter instruction-tuned model from Mistral AI with strong general + performance. Weights are stored locally after a one-time download from + HuggingFace. - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model picks the most likely token (deterministic). " - "Around 0.7 balances quality and creativity. At 1.0 outputs are " - "maximally varied." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 el modelo elige el token más probable (determinista). " - "Alrededor de 0.7 equilibra calidad y creatividad. En 1.0 las salidas " - "son máximamente variadas." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade " - "(intervalo 0.0-1.0). " - "Em 0.0 o modelo escolhe o token mais provável (determinístico). " - "Em torno de 0.7 equilibra qualidade e criatividade. Em 1.0 as saídas " - "são maximamente variadas." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell den wahrscheinlichsten Token " - "(deterministisch). " - "Ca. 0.7 balanciert Qualität und Kreativität. Bei 1.0 sind Ausgaben " - "maximal variiert." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型选择最可能的 token(确定性)。" - "0.7 左右在质量与创造性之间取得平衡。1.0 时输出变化最大。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desencorajam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据频率对已出现在输出中的 token 施加惩罚(范围 0.0-2.0)。" - "较高值可抑制重复。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalidade de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "bartowski/Mistral-7B-Instruct-v0.3-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 4372812000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#ff6f00" + DISPLAY_NAME = MultilingualString( + en="Mistral 7B Instruct v0.3", + es="Mistral 7B Instruct v0.3", + pt="Mistral 7B Instruct v0.3", + de="Mistral 7B Instruct v0.3", + zh="Mistral 7B Instruct v0.3", + ) + DESCRIPTION = MultilingualString( + en=( + "Mistral 7B Instruct v0.3 is a 7B-parameter instruction-tuned language " + "model from Mistral AI, loaded as a Q4_K_M GGUF for efficient CPU or GPU " + "inference. It offers strong general reasoning and generation quality. " + "Model available at " + "https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=131072), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including prompt and " - "response. Mistral-7B supports up to 32K tokens; Mistral-Nemo " - "supports up to 128K tokens." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Mistral-7B soporta hasta 32K tokens; Mistral-Nemo " - "soporta hasta 128K tokens." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Mistral-7B suporta até 32K tokens; Mistral-Nemo " - "suporta até 128K tokens." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabeaufforderung und Antwort. Mistral-7B unterstützt" - "bis zu 32K Token; Mistral-Nemo bis zu 128K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含提示词和回复。" - "Mistral-7B 支持最多 32K token;Mistral-Nemo 支持最多 128K token。" - ), + es=( + "Mistral 7B Instruct v0.3 es un modelo de lenguaje de 7B parametros " + "ajustado para instrucciones por Mistral AI, cargado como GGUF Q4_K_M " + "para inferencia eficiente en CPU o GPU. Ofrece solida capacidad de " + "razonamiento y calidad de generacion." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Mistral 7B Instruct v0.3 e um modelo de linguagem de 7B parametros " + "ajustado para instrucoes pela Mistral AI, carregado como GGUF Q4_K_M " + "para inferencia eficiente em CPU ou GPU. Oferece solida capacidade de " + "raciocinio e qualidade de geracao." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM. A GPU option offloads all layers for faster inference." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM. Una opción de GPU descarga todas las capas para " - "inferencia más rápida." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo em RAM. Uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM aus. Eine GPU-Option lagert alle Schichten für " - "schnellere Inferenz aus." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 完全在内存中运行模型。" - "选择 GPU 选项可卸载所有层以加快推理速度。" - ), + de=( + "Mistral 7B Instruct v0.3 ist ein 7B-Parameter-Instruktionsmodell von " + "Mistral AI, als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz " + "geladen. Es bietet starke allgemeine Schlussfolgerungs- und " + "Generierungsqualitaet." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Mistral 7B Instruct v0.3 是 Mistral AI 推出的 70 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "它具备强大的通用推理和生成质量。" ), - ) # type: ignore - + ) -class MistralModel(TextToTextGenerationTaskModel): - """Mistral Instruct model for open-ended text generation via llama.cpp. - Mistral is a 7B-parameter transformer language model developed by Mistral AI, - designed to deliver high performance with efficient inference. It uses grouped- - query attention (GQA) for faster decoding and sliding-window attention (SWA) to - handle long contexts efficiently. The 12B Mistral-Nemo variant, developed jointly - with NVIDIA, extends the context window to 128 K tokens and improves multilingual - capability. +class MistralNemoInstruct2407(GGUFTextGenerationModel): + """Mistral Nemo Instruct 2407 GGUF checkpoint (Q4_K_M quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``, - allowing CPU and GPU inference without requiring a full PyTorch stack. + A 12B-parameter instruction-tuned model from Mistral AI and NVIDIA with a + large context window. This is a heavy model that benefits from a GPU. + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Jiang et al. (2023) "Mistral 7B" https://arxiv.org/abs/2310.06825 - - [2] https://huggingface.co/mistralai + - https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF """ - SCHEMA = MistralSchema + REPO_ID = "bartowski/Mistral-Nemo-Instruct-2407-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 7477208192 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#ff6f00" - DISPLAY_NAME: str = MultilingualString( - en="Mistral Model", - es="Modelo Mistral", - pt="Modelo Mistral", - de="Mistral-Modell", - zh="Mistral 模型", + DISPLAY_NAME = MultilingualString( + en="Mistral Nemo Instruct 2407", + es="Mistral Nemo Instruct 2407", + pt="Mistral Nemo Instruct 2407", + de="Mistral Nemo Instruct 2407", + zh="Mistral Nemo Instruct 2407", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Mistral instruction-tuned models by Mistral AI, loaded in GGUF format " - "for efficient CPU and GPU inference via the llama.cpp library. Mistral " - "models are known for strong performance relative to their parameter count " - "and efficient inference. Supports multi-turn conversation, reasoning, " - "and general text generation. Available in 7B (Mistral-7B-v0.3) and 12B " - "(Mistral-Nemo-2407) variants. Models hosted at " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 is a 12B-parameter instruction-tuned language " + "model built by Mistral AI and NVIDIA, loaded as a Q4_K_M GGUF. It offers " + "high generation quality and a large context window, and is the heaviest " + "text-generation model in DashAI; a GPU is recommended. Model available at " + "https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF." ), es=( - "Modelos ajustados para instrucciones de Mistral AI, cargados en formato " - "GGUF para inferencia eficiente en CPU y GPU mediante llama.cpp. " - "Los modelos " - "Mistral son conocidos por su fuerte rendimiento relativo a su cantidad de " - "parámetros e inferencia eficiente. Soporta conversación multi-turno, " - "razonamiento y generación de texto en general. Disponible en variantes de " - "7B (Mistral-7B-v0.3) y 12B (Mistral-Nemo-2407). Modelos en " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 es un modelo de lenguaje de 12B parametros " + "ajustado para instrucciones, creado por Mistral AI y NVIDIA, cargado como " + "GGUF Q4_K_M. Ofrece alta calidad de generacion y una gran ventana de " + "contexto; es el modelo mas pesado de DashAI y se recomienda una GPU." ), pt=( - "Modelos ajustados para instruções da Mistral AI, carregados em formato " - "GGUF para inferência eficiente em CPU e GPU via llama.cpp. Os modelos " - "Mistral são conhecidos pelo forte desempenho em relação à sua quantidade " - "de parâmetros e inferência eficiente. Suporta conversação multi-turno, " - "raciocínio e geração de texto em geral. Disponível nas variantes de " - "7B (Mistral-7B-v0.3) e 12B (Mistral-Nemo-2407). Modelos em " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 e um modelo de linguagem de 12B parametros " + "ajustado para instrucoes, criado pela Mistral AI e NVIDIA, carregado como " + "GGUF Q4_K_M. Oferece alta qualidade de geracao e uma grande janela de " + "contexto; e o modelo mais pesado do DashAI e uma GPU e recomendada." ), de=( - "Instruktionsoptimierte Mistral-Modelle von Mistral AI, im GGUF-Format " - "für effiziente CPU- und GPU-Inferenz über die llama.cpp-Bibliothek. " - "Mistral-Modelle sind bekannt für starke Leistung relativ zu ihrer " - "Parameteranzahl und effizienter Inferenz. Unterstützt Mehrfachdialog, " - "Schlussfolgerung und allgemeine Textgenerierung. Verfügbar in 7B " - "(Mistral-7B-v0.3) und 12B (Mistral-Nemo-2407) Varianten. Modelle unter " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 ist ein 12B-Parameter-Instruktionsmodell von " + "Mistral AI und NVIDIA, als Q4_K_M-GGUF geladen. Es bietet hohe " + "Generierungsqualitaet und ein grosses Kontextfenster und ist das " + "schwerste Textgenerierungsmodell in DashAI; eine GPU wird empfohlen." ), zh=( - "Mistral AI 的指令微调模型,以 GGUF 格式加载," - "通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理和通用文本生成。提供 7B 和 12B 两种规格。" + "Mistral Nemo Instruct 2407 是 Mistral AI 与 NVIDIA 共同打造的 " + "120 亿参数指令微调语言模型,以 Q4_K_M GGUF 格式加载。" + "它具有高生成质量和大上下文窗口,是 DashAI 中最重的文本生成模型," + "建议使用 GPU。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Mistral Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"bartowski/Mistral-7B-Instruct-v0.3-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "bartowski/Mistral-7B-Instruct-v0.3-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = "*Q4_K_M.gguf" - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/mixtral_model.py b/DashAI/back/models/hugging_face/mixtral_model.py index 402eb281a..78f70b4bc 100644 --- a/DashAI/back/models/hugging_face/mixtral_model.py +++ b/DashAI/back/models/hugging_face/mixtral_model.py @@ -1,524 +1,132 @@ -from typing import List +"""Mixtral 8x7B Instruct GGUF checkpoint subclasses for DashAI. + +Mixtral 8x7B is a single Sparse Mixture-of-Experts repo published in several +quantizations. Each quantization is exposed as its own downloadable component so +the user fetches only the one GGUF file they intend to run. +""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) +_MIXTRAL_REPO = "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF" -class MixtralSchema(BaseSchema): - """Schema for MixtralModel hyperparameters. - Configures the checkpoint variant (with optional GGUF filename override), - generation length, sampling temperature, frequency penalty, context window, - and target device for Mixtral Sparse-MoE models loaded via - ``llama-cpp-python``. - """ - - model_name: schema_field( - enum_field( - enum=[ - "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF", - ] - ), - placeholder="mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF", - description=MultilingualString( - en=( - "The Mixtral Instruct checkpoint to load in GGUF format. " - "'Mixtral-8x7B-Instruct-v0.1' is a Sparse Mixture-of-Experts (SMoE) " - "model with 8 expert networks of 7B parameters each, activating 2 " - "experts per token. It achieves quality comparable to larger dense " - "models while being more efficient at inference. " - "Warning: this model requires ~26 GB of RAM for the Q4_K_M " - "quantization." - ), - es=( - "El checkpoint Mixtral Instruct a cargar en formato GGUF. " - "'Mixtral-8x7B-Instruct-v0.1' es un modelo de Mezcla Dispersa de " - "Expertos (SMoE) con 8 redes expertas de 7B parámetros cada una, " - "activando 2 expertos por token. Logra calidad comparable a modelos " - "densos más grandes siendo más eficiente en inferencia. " - "Advertencia: este modelo requiere ~26 GB de RAM para la " - "cuantización Q4_K_M." - ), - pt=( - "O checkpoint Mixtral Instruct a carregar em formato GGUF. " - "'Mixtral-8x7B-Instruct-v0.1' é um modelo de Mistura Esparsa de " - "Especialistas (SMoE) com 8 redes especialistas de 7B parâmetros cada, " - "ativando 2 especialistas por token. Alcança qualidade comparável a " - "modelos densos maiores sendo mais eficiente na inferência. " - "Aviso: este modelo requer ~26 GB de RAM para a " - "quantização Q4_K_M." - ), - de=( - "Der im GGUF-Format zu ladende Mixtral Instruct-Checkpoint. " - "'Mixtral-8x7B-Instruct-v0.1' ist ein Sparse Mixture-of-Experts " - "(SMoE)-Modell mit 8 Expertennetzwerken à 7B Parameter, das 2 " - "Experten pro Token aktiviert. Es erreicht eine mit größeren dichten " - "Modellen vergleichbare Qualität bei effizienterer Inferenz. " - "Warnung: dieses Modell benötigt ~26 GB RAM für die " - "Q4_K_M-Quantisierung." - ), - zh=( - "以 GGUF 格式加载的 Mixtral Instruct 检查点。" - "'Mixtral-8x7B-Instruct-v0.1' 是一个稀疏混合专家(SMoE)模型," - "包含 8 个各 70 亿参数的专家网络,每个 token 激活 2 个专家。" - "推理效率高于同等质量的稠密模型。" - "警告:Q4_K_M 量化需要约 26 GB 内存。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Mixtral8x7BInstructQ4KM(GGUFTextGenerationModel): + """Mixtral 8x7B Instruct GGUF checkpoint (Q4_K_M quantization). - filename: schema_field( - enum_field( - enum=[ - "Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q3_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q4_0.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q4_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q5_0.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q5_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q6_K.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q8_0.gguf", - ] - ), - placeholder="Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf", - description=MultilingualString( - en=( - "The specific GGUF file to load for the Mixtral model. The different " - "quantization levels (Q2_K, Q3_K_M, Q4_0, Q4_K_M, Q5_0, Q5_K_M, " - "Q6_K, Q8_0) represent various trade-offs between model size, " - "inference speed, and output quality. Q4_K_M is a popular choice " - "for balancing performance and resource requirements." - ), - es=( - "El archivo GGUF específico a cargar para el modelo Mixtral. Los " - "diferentes niveles de cuantización (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) representan varios compromisos entre " - "tamaño del modelo, velocidad de inferencia y calidad de salida. " - "Q4_K_M es una opción popular para equilibrar rendimiento y " - "requisitos de recursos." - ), - pt=( - "O arquivo GGUF específico a carregar para o modelo Mixtral. Os " - "diferentes níveis de quantização (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) representam vários compromissos entre " - "tamanho do modelo, velocidade de inferência e qualidade de saída. " - "Q4_K_M é uma escolha popular para equilibrar desempenho e " - "requisitos de recursos." - ), - de=( - "Die zu ladende spezifische GGUF-Datei für das Mixtral-Modell. " - "Die verschiedenen Quantisierungsstufen (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) stellen verschiedene Kompromisse zwischen " - "Modellgröße, Inferenzgeschwindigkeit und Ausgabequalität dar. " - "Q4_K_M ist eine beliebte Wahl für ausgewogene Leistung und " - "Ressourcenbedarf." - ), - zh=( - "为 Mixtral 模型加载的具体 GGUF 文件。" - "不同量化级别(Q2_K、Q3_K_M、Q4_0、Q4_K_M、Q5_0、Q5_K_M、Q6_K、Q8_0)" - "在模型大小、推理速度和输出质量之间存在不同权衡。" - "Q4_K_M 是兼顾性能与资源需求的常用选择。" - ), - ), - alias=MultilingualString( - en="Filename", - es="Nombre del archivo", - pt="Nome do archivo", - de="Dateiname", - zh="文件名", - ), - ) # type: ignore + A Sparse Mixture-of-Experts model (8 experts of 7B parameters, 2 active per + token) from Mistral AI. The Q4_K_M quantization balances quality and size + and requires roughly 26 GB of RAM. Weights are stored locally after a + one-time download from HuggingFace. - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas " - "o código." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas " - "ou código." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "约 1 token ≈ 0.75 个英文单词。" - "短回答设为 100-200,详细说明或代码设为 500-1000。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore - - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 outputs are deterministic. Around 0.7 balances quality and " - "creativity." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 las salidas son deterministas. Alrededor de 0.7 equilibra " - "calidad y creatividad." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade " - "(intervalo 0.0-1.0). " - "Em 0.0 as saídas são determinísticas. Em torno de 0.7 equilibra " - "qualidade e criatividade." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 sind die Ausgaben deterministisch. Um 0.7 balanciert " - "Qualität und Kreativität." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时输出确定性最强,0.7 左右可平衡质量与创造性。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 + - https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desestimulam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据频率对已出现在输出中的 token 施加惩罚(范围 0.0-2.0)。" - "较高的值可抑制重复内容。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = _MIXTRAL_REPO + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 28448468384 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#4a148c" + DISPLAY_NAME = MultilingualString( + en="Mixtral 8x7B Instruct (Q4_K_M)", + es="Mixtral 8x7B Instruct (Q4_K_M)", + pt="Mixtral 8x7B Instruct (Q4_K_M)", + de="Mixtral 8x7B Instruct (Q4_K_M)", + zh="Mixtral 8x7B Instruct (Q4_K_M)", + ) + DESCRIPTION = MultilingualString( + en=( + "Mixtral 8x7B Instruct is a Sparse Mixture-of-Experts model by Mistral " + "AI (8 experts of 7B parameters, 2 active per token), loaded as a Q4_K_M " + "GGUF for a balance of quality and size. It matches larger dense models " + "on many tasks. Warning: requires ~26 GB of RAM; a GPU with >= 24 GB " + "VRAM is recommended. Model available at " + "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=32768), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Mixtral 8x7B supports " - "up to 32K tokens natively." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Mixtral 8x7B soporta hasta 32K tokens de forma nativa." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Mixtral 8x7B suporta até 32K tokens nativamente." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. " - "Mixtral 8x7B unterstützt nativ bis zu 32K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示和生成响应。" - "Mixtral 8x7B 原生支持最多 32K token。" - ), + es=( + "Mixtral 8x7B Instruct es un modelo de Mezcla Dispersa de Expertos de " + "Mistral AI (8 expertos de 7B parametros, 2 activos por token), cargado " + "como GGUF Q4_K_M para equilibrar calidad y tamano. Advertencia: " + "requiere ~26 GB de RAM; se recomienda una GPU con >= 24 GB de VRAM." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Mixtral 8x7B Instruct e um modelo de Mistura Esparsa de Especialistas " + "da Mistral AI (8 especialistas de 7B parametros, 2 ativos por token), " + "carregado como GGUF Q4_K_M para equilibrar qualidade e tamanho. Aviso: " + "requer ~26 GB de RAM; recomenda-se uma GPU com >= 24 GB de VRAM." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM. A GPU option offloads all layers for faster inference. " - "Due to the large size of Mixtral, a GPU with at least 24 GB VRAM " - "is recommended for full GPU offloading." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM. Una opción de GPU descarga todas las capas para " - "inferencia más rápida. Debido al gran tamaño de Mixtral, " - "se recomienda " - "una GPU con al menos 24 GB de VRAM para descarga completa." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo na RAM. Uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida. Devido ao grande tamanho do Mixtral, " - "recomenda-se " - "uma GPU com pelo menos 24 GB de VRAM para descarregamento completo." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "im RAM aus. Eine GPU-Option lagert alle Schichten für schnellere " - "Inferenz aus. Aufgrund der Größe von Mixtral wird eine GPU mit " - "mindestens 24 GB VRAM für vollständiges GPU-Offloading empfohlen." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 在内存中运行模型。" - "GPU 选项可将所有层卸载以加快推理速度。" - "由于 Mixtral 体量较大,完整 GPU 卸载建议使用至少 24 GB 显存的 GPU。" - ), + de=( + "Mixtral 8x7B Instruct ist ein Sparse-Mixture-of-Experts-Modell von " + "Mistral AI (8 Experten mit 7B Parametern, 2 pro Token aktiv), als " + "Q4_K_M-GGUF fuer ein Gleichgewicht aus Qualitaet und Groesse geladen. " + "Warnung: benoetigt ~26 GB RAM; eine GPU mit >= 24 GB VRAM wird empfohlen." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家模型" + "(8 个 70 亿参数专家,每 token 激活 2 个)," + "以 Q4_K_M GGUF 格式加载,兼顾质量与体积。" + "警告:需要约 26 GB 内存;建议使用显存不低于 24 GB 的 GPU。" ), - ) # type: ignore - + ) -class MixtralModel(TextToTextGenerationTaskModel): - """Mixtral Sparse Mixture-of-Experts (SMoE) model for text generation via llama.cpp. - Mixtral 8x7B is a transformer language model with 8 expert feed-forward - networks per layer; only 2 experts are activated per token, giving it the - computational cost of a 12B-parameter dense model while retaining capacity - equivalent to a 47B model. It matches or surpasses Llama 2 70B and GPT-3.5 - on most benchmarks. +class Mixtral8x7BInstructQ2K(GGUFTextGenerationModel): + """Mixtral 8x7B Instruct GGUF checkpoint (Q2_K quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``. - The Q4_K_M quantization requires approximately 26 GB of RAM. + The smallest quantization of the Mixtral 8x7B Sparse Mixture-of-Experts + model, trading some quality for a lower memory footprint (roughly 16 GB). + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 - - [2] https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1 + - Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 + - https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF """ - SCHEMA = MixtralSchema + REPO_ID = _MIXTRAL_REPO + GGUF_PATTERN = "*Q2_K.gguf" + DOWNLOAD_SIZE_BYTES = 17311231392 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#4a148c" - DISPLAY_NAME: str = MultilingualString( - en="Mixtral Model", - es="Modelo Mixtral", - pt="Modelo Mixtral", - de="Mixtral-Modell", - zh="Mixtral 模型", + DISPLAY_NAME = MultilingualString( + en="Mixtral 8x7B Instruct (Q2_K)", + es="Mixtral 8x7B Instruct (Q2_K)", + pt="Mixtral 8x7B Instruct (Q2_K)", + de="Mixtral 8x7B Instruct (Q2_K)", + zh="Mixtral 8x7B Instruct (Q2_K)", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Mixtral 8x7B Instruct, a Sparse Mixture-of-Experts (SMoE) model by " - "Mistral AI, loaded in GGUF format for efficient CPU and GPU inference " - "via the llama.cpp library. The model uses 8 expert networks of 7B " - "parameters each, activating only 2 experts per token, achieving " - "performance comparable to larger dense models while being more efficient " - "at inference. Supports multi-turn conversation, reasoning, coding, and " - "general text generation. Warning: requires ~26 GB of RAM for the Q4_K_M " - "quantization. Model hosted at " + "Mixtral 8x7B Instruct is a Sparse Mixture-of-Experts model by Mistral " + "AI (8 experts of 7B parameters, 2 active per token). This Q2_K " + "quantization is the smallest variant (~16 GB), trading some output " + "quality for lower memory use. Model available at " "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." ), es=( - "Mixtral 8x7B Instruct, un modelo de Mezcla Dispersa de Expertos (SMoE) " - "de Mistral AI, cargado en formato GGUF para inferencia eficiente en CPU " - "y GPU mediante llama.cpp. El modelo usa 8 redes expertas de 7B parámetros " - "cada una, activando solo 2 expertos por token, logrando un rendimiento " - "comparable a modelos densos más grandes " - "siendo más eficiente en inferencia. " - "Soporta conversación multi-turno, razonamiento, programación y generación " - "de texto en general. Advertencia: requiere ~26 GB de RAM para la " - "cuantización Q4_K_M. Modelo en " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct es un modelo de Mezcla Dispersa de Expertos de " + "Mistral AI. Esta cuantizacion Q2_K es la variante mas pequena (~16 GB), " + "sacrificando algo de calidad por menor uso de memoria." ), pt=( - "Mixtral 8x7B Instruct, um modelo de Mistura Esparsa de Especialistas " - "(SMoE) da Mistral AI, carregado em formato GGUF para inferência eficiente " - "em CPU e GPU via biblioteca llama.cpp. O modelo usa 8 redes especialistas " - "de 7B parâmetros cada, ativando apenas 2 especialistas por token, " - "alcançando desempenho comparável a modelos densos maiores sendo mais " - "eficiente na inferência. Suporta conversa multi-turno, raciocínio, " - "programação e geração de texto em geral. Aviso: requer ~26 GB de RAM para " - "a quantização Q4_K_M. Modelo disponível em " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct e um modelo de Mistura Esparsa de Especialistas " + "da Mistral AI. Esta quantizacao Q2_K e a variante menor (~16 GB), " + "trocando alguma qualidade por menor uso de memoria." ), de=( - "Mixtral 8x7B Instruct, ein Sparse Mixture-of-Experts (SMoE)-Modell von " - "Mistral AI, im GGUF-Format für effiziente CPU- und GPU-Inferenz über die " - "llama.cpp-Bibliothek geladen. Das Modell nutzt 8 Expertennetzwerke à 7B " - "Parameter und aktiviert nur 2 Experten pro Token, was mit größeren dichten" - "Modellen vergleichbare Leistung bei effizienterer Inferenz ermöglicht. " - "Unterstützt Mehrfachdialog, Schlussfolgerung, Programmierung und " - "allgemeine " - "Textgenerierung. Warnung: erfordert ~26 GB RAM für die " - "Q4_K_M-Quantisierung. " - "Modell unter " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct ist ein Sparse-Mixture-of-Experts-Modell von " + "Mistral AI. Diese Q2_K-Quantisierung ist die kleinste Variante " + "(~16 GB) und tauscht etwas Qualitaet gegen geringeren Speicherbedarf." ), zh=( - "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家(SMoE)模型," - "以 GGUF 格式加载,通过 llama.cpp 库高效推理。" - "支持多轮对话、推理、编程和通用文本生成。" - "注意:Q4_K_M 量化需要约 26 GB 内存。" + "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家模型。" + "此 Q2_K 量化是最小的变体(约 16 GB)," + "以部分输出质量换取更低的内存占用。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Mixtral 8x7B Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to - ``"mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF"``. - filename : str, optional - Specific GGUF quantization file to load (e.g. - ``"mixtral-8x7b-instruct-v0.1.Q4_K_M.gguf"``). Defaults to - the Q2_K variant. Higher quantizations use more RAM but - produce better output quality. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = kwargs.get("filename", "Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/modernbert_transformer.py b/DashAI/back/models/hugging_face/modernbert_transformer.py index ae2be1fff..af58e5b2a 100644 --- a/DashAI/back/models/hugging_face/modernbert_transformer.py +++ b/DashAI/back/models/hugging_face/modernbert_transformer.py @@ -55,5 +55,6 @@ class ModernBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = ModernBertTransformerSchema MODEL_NAME: str = "answerdotai/ModernBERT-base" + DOWNLOAD_SIZE_BYTES: int = 1199464688 MAX_TOKEN_LENGTH: int = 8192 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_modernbert" diff --git a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py index bd1e44bfe..a3359d142 100644 --- a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py +++ b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py @@ -33,32 +33,33 @@ class MultilingualBertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "BERT pretrained on 104 languages for multilingual text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT preentrenado en 104 idiomas para clasificación de texto " "multilingüe. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT pré-treinado em 104 idiomas para classificação de texto " "multilingual. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "BERT vortrainiert auf 104 Sprachen für mehrsprachige Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在 104 种语言上预训练的 BERT,用于多语言文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#283593" ICON: str = "Translate" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-multilingual-cased" + DOWNLOAD_SIZE_BYTES: int = 1431570300 TEMP_CHECKPOINT_DIR: str = ( "DashAI/back/user_models/temp_checkpoints_multilingual_bert" ) diff --git a/DashAI/back/models/hugging_face/nllb_transformer.py b/DashAI/back/models/hugging_face/nllb_transformer.py index ab7a5556c..9fbe23e81 100644 --- a/DashAI/back/models/hugging_face/nllb_transformer.py +++ b/DashAI/back/models/hugging_face/nllb_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import schema_field, string_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -121,7 +124,7 @@ class NllbTransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class NllbTransformer(TranslationModel): +class NllbTransformer(HFPretrainedDownloadMixin, TranslationModel): """Pretrained transformer for configurable multilingual translation. This model fine-tunes the ``facebook/nllb-200-distilled-600M`` checkpoint from @@ -201,7 +204,10 @@ def _resolve_language_token_id(self, language_code: str, field_name: str) -> int raise ValueError(f"Unsupported {field_name} '{language_code}'.") - def __init__(self, model=None, **kwargs): + MODEL_NAME: str = "facebook/nllb-200-distilled-600M" + DOWNLOAD_SIZE_BYTES: int = 2482655255 + + def __init__(self, model=None, pretrained_dir=None, **kwargs): """Initialize the NLLB tokenizer and model. Downloads the ``facebook/nllb-200-distilled-600M`` tokenizer and, @@ -231,7 +237,7 @@ def __init__(self, model=None, **kwargs): from transformers import AutoTokenizer - self.model_name = "facebook/nllb-200-distilled-600M" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.source_language = kwargs.get("source_language", "spa_Latn") @@ -533,6 +539,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -573,6 +580,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py index acc031fa9..8007c5e97 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py @@ -29,6 +29,7 @@ class OpusMtEnDeTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-de" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-de" SCHEMA = OpusMtEnDeTransformerSchema + DOWNLOAD_SIZE_BYTES = 300772148 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-De Transformer", es="Transformer Opus MT En-De", @@ -39,24 +40,24 @@ class OpusMtEnDeTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to German translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-alemán. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-alemão. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Deutsch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到德语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#455A64" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py index 82f827345..3ed97502d 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py @@ -270,6 +270,7 @@ class OpusMtEnESTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-es" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-es" SCHEMA = OpusMtEnESTransformerSchema + DOWNLOAD_SIZE_BYTES = 315310815 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Es Transformer", es="Transformer Opus MT En-Es", @@ -280,24 +281,24 @@ class OpusMtEnESTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to Spanish translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Spanisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到西班牙语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#FFA500" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py index ba45b6b3d..18c791f16 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py @@ -29,6 +29,7 @@ class OpusMtEnFrTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-fr" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-fr" SCHEMA = OpusMtEnFrTransformerSchema + DOWNLOAD_SIZE_BYTES = 303750994 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Fr Transformer", es="Transformer Opus MT En-Fr", @@ -39,24 +40,24 @@ class OpusMtEnFrTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to French translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-francés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-francês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Französisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到法语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#1976D2" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py deleted file mode 100644 index 17a1f2076..000000000 --- a/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py +++ /dev/null @@ -1,63 +0,0 @@ -"""OpusMtEnPtTransformer model for English to Portuguese translation.""" - -from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.hugging_face.base_opus_mt_transformer import ( - OpusMtTransformerMixin, -) -from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( - OpusMtEnESTransformerSchema, -) - - -class OpusMtEnPtTransformerSchema(OpusMtEnESTransformerSchema): - """Schema for the English to Portuguese Opus-MT model.""" - - -class OpusMtEnPtTransformer(OpusMtTransformerMixin): - """Pretrained transformer for English to Portuguese translation. - - Fine-tunes the Helsinki-NLP ``opus-mt-en-pt`` checkpoint, a MarianMT - seq2seq model trained on parallel English to Portuguese corpora from the OPUS - collection. - - References - ---------- - - [1] https://huggingface.co/Helsinki-NLP/opus-mt-en-pt - - [2] https://opus.nlpl.eu/ - """ - - MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-pt" - TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-pt" - SCHEMA = OpusMtEnPtTransformerSchema - DISPLAY_NAME: str = MultilingualString( - en="Opus MT En-Pt Transformer", - es="Transformer Opus MT En-Pt", - pt="Transformer Opus MT En-Pt", - de="Opus MT En-Pt Transformer", - zh="Opus MT 英葡翻译 Transformer", - ) - DESCRIPTION: str = MultilingualString( - en=( - "Pretrained transformer for English to Portuguese translation. " - "Downloads weights from Hugging Face on first use (internet required)." - ), - es=( - "Transformer preentrenado para traducción inglés-portugués. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." - ), - pt=( - "Transformer pré-treinado para tradução inglês-português. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." - ), - de=( - "Vortrainierter Transformer für Englisch-Portugiesisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " - "(Internet erforderlich)." - ), - zh=( - "用于英语到葡萄牙语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" - ), - ) - COLOR: str = "#2E7D32" - ICON: str = "Translate" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py new file mode 100644 index 000000000..c6a090c5d --- /dev/null +++ b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py @@ -0,0 +1,225 @@ +"""OpusMtEnRoaTransformer model for English to Romance translation. + +Unlike the single language-pair Opus-MT wrappers, the ``opus-mt-en-roa`` +checkpoint is multi-target: it translates English into any of several Romance +languages, selected by prepending a sentence-initial language token +(``>>id<<``) to every source sentence. All of that extra handling lives in this +file so the shared :class:`OpusMtTransformerMixin` stays single-pair. +""" + +from pathlib import Path +from typing import TYPE_CHECKING, Optional, Union + +from DashAI.back.core.schema_fields import enum_field, schema_field +from DashAI.back.core.utils import MultilingualString +from DashAI.back.models.hugging_face.base_opus_mt_transformer import ( + OpusMtTransformerMixin, +) +from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( + OpusMtEnESTransformerSchema, +) + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +#: Human readable target language -> Marian language token id. Every id below is +#: a valid sentence-initial token in the ``opus-mt-en-roa`` vocabulary. +TARGET_LANG_TOKENS = { + "Portuguese": "por", + "Spanish": "spa", + "French": "fra", + "Italian": "ita", + "Romanian": "ron", + "Catalan": "cat", + "Galician": "glg", +} + + +class OpusMtEnRoaTransformerSchema(OpusMtEnESTransformerSchema): + """Schema for the English to Romance Opus-MT model. + + Adds the ``target_language`` selector on top of the shared Opus-MT training + hyperparameters, since this checkpoint can translate into several languages. + """ + + target_language: schema_field( + enum_field(enum=list(TARGET_LANG_TOKENS)), + placeholder="French", + description=MultilingualString( + en="Romance language to translate the English input into.", + es="Lengua romance a la que traducir la entrada en inglés.", + pt="Língua românica para a qual traduzir a entrada em inglês.", + de="Romanische Sprache, in die die englische Eingabe übersetzt wird.", + zh="将英语输入翻译成的目标罗曼语。", + ), + alias=MultilingualString( + en="Target language", + es="Idioma de destino", + pt="Idioma de destino", + de="Zielsprache", + zh="目标语言", + ), + ) # type: ignore + + +class OpusMtEnRoaTransformer(OpusMtTransformerMixin): + """Pretrained transformer for English to Romance translation. + + Fine-tunes the Helsinki-NLP ``opus-mt-en-roa`` checkpoint, a multi-target + MarianMT seq2seq model trained on parallel English to Romance corpora from + the OPUS collection. The desired output language is chosen via the + ``target_language`` parameter and injected as a sentence-initial ``>>id<<`` + token on every source sentence (required by this checkpoint), covering + English to Portuguese among the other Romance targets. + + References + ---------- + - [1] https://huggingface.co/Helsinki-NLP/opus-mt-en-roa + - [2] https://opus.nlpl.eu/ + """ + + MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-roa" + TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-roa" + SCHEMA = OpusMtEnRoaTransformerSchema + DOWNLOAD_SIZE_BYTES = 297844640 + DISPLAY_NAME: str = MultilingualString( + en="Opus MT En-Roa Transformer", + es="Transformer Opus MT En-Roa", + pt="Transformer Opus MT En-Roa", + de="Opus MT En-Roa Transformer", + zh="Opus MT 英语-罗曼语翻译 Transformer", + ) + DESCRIPTION: str = MultilingualString( + en=( + "Pretrained transformer for English to Romance translation " + "(Portuguese, Spanish, French, Italian, Romanian, Catalan, " + "Galician), selected via the target language parameter. Download " + "its weights from Hugging Face before use (internet required)." + ), + es=( + "Transformer preentrenado para traducción del inglés a lenguas " + "romances (portugués, español, francés, italiano, rumano, catalán, " + "gallego), seleccionadas con el parámetro de idioma de destino. " + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." + ), + pt=( + "Transformer pré-treinado para tradução do inglês para línguas " + "românicas (português, espanhol, francês, italiano, romeno, catalão, " + "galego), selecionadas pelo parâmetro de idioma de destino. Baixe " + "seus pesos do Hugging Face antes de usar (requer internet)." + ), + de=( + "Vortrainierter Transformer für die Übersetzung von Englisch in " + "romanische Sprachen (Portugiesisch, Spanisch, Französisch, " + "Italienisch, Rumänisch, Katalanisch, Galicisch), ausgewählt über " + "den Zielsprachenparameter. Lädt die Gewichte vor der Nutzung von " + "Hugging Face herunter (Internet erforderlich)." + ), + zh=( + "用于英语到罗曼语翻译的预训练 Transformer(葡萄牙语、西班牙语、法语、" + "意大利语、罗马尼亚语、加泰罗尼亚语、加利西亚语),通过目标语言参数选择。" + "使用前需从 Hugging Face 下载权重(需要网络)。" + ), + ) + COLOR: str = "#5E35B1" + ICON: str = "Translate" + + def __init__(self, model=None, **kwargs): + """Initialize the base model and build the language-token prefix. + + Parameters + ---------- + model : transformers.PreTrainedModel or None + Preloaded model to reuse instead of downloading weights. + **kwargs + Training hyperparameters plus ``target_language`` (a key of + :data:`TARGET_LANG_TOKENS`). + """ + # The shared mixin.load() rebuilds the model from saved hyperparameters + # but has no knowledge of target_language, so it is absent from kwargs on + # a load and would fail the required field schema validation. Supply a + # placeholder to pass validation; load() restores the real value from + # disk immediately after construction. + kwargs.setdefault("target_language", next(iter(TARGET_LANG_TOKENS))) + super().__init__(model=model, **kwargs) + self.target_language = kwargs.get("target_language") + self.lang_prefix = self._build_lang_prefix(self.target_language) + + @staticmethod + def _build_lang_prefix(target_language: Optional[str]) -> str: + """Return the ``>>id<< `` token for a target language, or ``""``.""" + if not target_language: + return "" + token = TARGET_LANG_TOKENS.get(target_language) + if token is None: + raise ValueError( + f"Unsupported target_language '{target_language}'. Valid " + f"options: {list(TARGET_LANG_TOKENS)}." + ) + return f">>{token}<< " + + def tokenize_data( + self, x: "DashAIDataset", y: Optional["DashAIDataset"] = None + ) -> "DashAIDataset": + """Tokenize like the base class but prepend the target language token. + + The ``opus-mt-en-roa`` checkpoint requires a sentence-initial ``>>id<<`` + token on the source to pick the output language; the target side is + left untouched. + """ + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + is_y = bool(y) + if not y: + y = DashAIDataset.from_list([{"foo": 0}] * len(x)) + + dataset = [] + input_column_name = x.column_names[0] + output_column_name = y.column_names[0] if is_y else None + + for i, input_sample in enumerate(x): + tokenized_input = self.tokenizer( + self.lang_prefix + input_sample[input_column_name], + truncation=True, + padding="max_length", + max_length=512, + ) + sample = { + "input_ids": tokenized_input["input_ids"], + "attention_mask": tokenized_input["attention_mask"], + } + if is_y: + output_sample = y[i] + tokenized_output = self.tokenizer( + output_sample[output_column_name], + truncation=True, + padding="max_length", + max_length=512, + ) + sample["labels"] = tokenized_output["input_ids"] + dataset.append(sample) + + return DashAIDataset.from_list(dataset) + + def save(self, filename: Union[str, "Path"]) -> None: + """Persist the model, recording the chosen target language.""" + from transformers import AutoConfig + + super().save(filename) + config = AutoConfig.from_pretrained(filename) + custom_params = getattr(config, "custom_params", {}) + custom_params["target_language"] = self.target_language + config.custom_params = custom_params + config.save_pretrained(filename) + + @classmethod + def load(cls, filename: Union[str, "Path"]): + """Restore a model instance and its target language prefix.""" + from transformers import AutoConfig + + loaded_model = super().load(filename) + config = AutoConfig.from_pretrained(filename) + target_language = getattr(config, "custom_params", {}).get("target_language") + loaded_model.target_language = target_language + loaded_model.lang_prefix = cls._build_lang_prefix(target_language) + return loaded_model diff --git a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py index c7bcc2e81..43d077460 100644 --- a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py @@ -32,6 +32,7 @@ class OpusMtEsENTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-es-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-es-en" SCHEMA = OpusMtEsENTransformerSchema + DOWNLOAD_SIZE_BYTES = 315310760 DISPLAY_NAME: str = MultilingualString( en="Opus MT Es-En Transformer", es="Transformer Opus MT Es-En", @@ -42,24 +43,24 @@ class OpusMtEsENTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for Spanish to English translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción español-inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução espanhol-inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Spanisch-Englisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于西班牙语到英语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#FF8A65" diff --git a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py index 903a3896c..94f77975b 100644 --- a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py @@ -29,6 +29,7 @@ class OpusMtFrEnTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-fr-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-fr-en" SCHEMA = OpusMtFrEnTransformerSchema + DOWNLOAD_SIZE_BYTES = 604554697 DISPLAY_NAME: str = MultilingualString( en="Opus MT Fr-En Transformer", es="Transformer Opus MT Fr-En", @@ -39,24 +40,24 @@ class OpusMtFrEnTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for French to English translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción francés-inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução francês-inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Französisch-Englisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于法语到英语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#0097A7" diff --git a/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py new file mode 100644 index 000000000..5d1f189f0 --- /dev/null +++ b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py @@ -0,0 +1,70 @@ +"""OpusMtRoaEnTransformer model for Romance to English translation.""" + +from DashAI.back.core.utils import MultilingualString +from DashAI.back.models.hugging_face.base_opus_mt_transformer import ( + OpusMtTransformerMixin, +) +from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( + OpusMtEnESTransformerSchema, +) + + +class OpusMtRoaEnTransformerSchema(OpusMtEnESTransformerSchema): + """Schema for the Romance to English Opus-MT model.""" + + +class OpusMtRoaEnTransformer(OpusMtTransformerMixin): + """Pretrained transformer for Romance to English translation. + + Fine-tunes the Helsinki-NLP ``opus-mt-roa-en`` checkpoint, a MarianMT + seq2seq model trained on parallel Romance to English corpora from the OPUS + collection. The target language is always English, so the source text is + fed as-is without a language token; this covers Portuguese to English + among the other Romance source languages. + + References + ---------- + - [1] https://huggingface.co/Helsinki-NLP/opus-mt-roa-en + - [2] https://opus.nlpl.eu/ + """ + + MODEL_NAME: str = "Helsinki-NLP/opus-mt-roa-en" + TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-roa-en" + SCHEMA = OpusMtRoaEnTransformerSchema + DOWNLOAD_SIZE_BYTES = 315135823 + DISPLAY_NAME: str = MultilingualString( + en="Opus MT Roa-En Transformer", + es="Transformer Opus MT Roa-En", + pt="Transformer Opus MT Roa-En", + de="Opus MT Roa-En Transformer", + zh="Opus MT 罗曼语-英语翻译 Transformer", + ) + DESCRIPTION: str = MultilingualString( + en=( + "Pretrained transformer for Romance to English translation " + "(includes Portuguese to English). Download its weights from " + "Hugging Face before use (internet required)." + ), + es=( + "Transformer preentrenado para traducción de lenguas romances al " + "inglés (incluye portugués a inglés). Descarga sus pesos de " + "Hugging Face antes de usarlo (requiere internet)." + ), + pt=( + "Transformer pré-treinado para tradução de línguas românicas para o " + "inglês (inclui português para inglês). Baixe seus pesos do " + "Hugging Face antes de usar (requer internet)." + ), + de=( + "Vortrainierter Transformer für die Übersetzung romanischer Sprachen " + "ins Englische (einschließlich Portugiesisch nach Englisch). Lädt " + "die Gewichte vor der Nutzung von Hugging Face herunter " + "(Internet erforderlich)." + ), + zh=( + "用于罗曼语到英语翻译的预训练 Transformer(包括葡萄牙语到英语)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" + ), + ) + COLOR: str = "#00796B" + ICON: str = "Translate" diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index e4ca5b6dd..20a5f02fb 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -18,7 +21,7 @@ class PixArtSigmaSchema(BaseSchema): """Configuration schema for PixArt-Sigma text-to-image generation. - Configures the checkpoint variant (``model_name``), prompt conditioning + Configures the checkpoint (``checkpoint``), prompt conditioning (``negative_prompt``), denoising schedule (``num_inference_steps``), classifier free guidance strength (``guidance_scale``), output dimensions (``width``, ``height``), reproducibility (``seed``), hardware target @@ -26,61 +29,43 @@ class PixArtSigmaSchema(BaseSchema): ``PixArtSigmaModel``. """ - model_name: schema_field( - enum_field( - enum=[ - "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", - "PixArt-alpha/PixArt-Sigma-XL-2-512-MS", - ] - ), - placeholder="PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", + checkpoint: schema_field( + enum_field(enum=["1024", "512"]), + placeholder="1024", description=MultilingualString( en=( - "The PixArt-Sigma checkpoint to load. " - "'PixArt-Sigma-XL-2-1024-MS' is the high resolution variant " - "trained at 1024px with multiscale support, delivering the best " - "image quality. " - "'PixArt-Sigma-XL-2-512-MS' is the 512px variant, faster and lighter " - "while still producing sharp results." + "Which PixArt-Sigma checkpoint to use: '1024' for best quality " + "at 1024x1024 px, or '512' for a faster, lighter model at " + "512x512 px. Both checkpoints are downloaded together." ), es=( - "El checkpoint PixArt-Sigma a cargar. " - "'PixArt-Sigma-XL-2-1024-MS' es la variante de alta resolución " - "entrenada a 1024px con soporte multiescala, entregando la mejor " - "calidad de imagen. " - "'PixArt-Sigma-XL-2-512-MS' es la variante de 512px, más rápida y " - "ligera manteniendo resultados nítidos." + "Qué checkpoint de PixArt-Sigma usar: '1024' para mejor calidad " + "a 1024x1024 px, o '512' para un modelo más rápido y ligero a " + "512x512 px. Ambos checkpoints se descargan juntos." ), pt=( - "O checkpoint PixArt-Sigma a carregar. " - "'PixArt-Sigma-XL-2-1024-MS' é a variante de alta resolução " - "treinada a 1024px com suporte multiescala, entregando a melhor " - "qualidade de imagem. " - "'PixArt-Sigma-XL-2-512-MS' é a variante de 512px, mais rápida e " - "leve, mantendo resultados nítidos." + "Qual checkpoint do PixArt-Sigma usar: '1024' para melhor " + "qualidade a 1024x1024 px, ou '512' para um modelo mais rápido " + "e leve a 512x512 px. Ambos os checkpoints são baixados juntos." ), de=( - "Der zu ladende PixArt-Sigma-Checkpoint. " - "'PixArt-Sigma-XL-2-1024-MS' ist die hochauflösende Variante, " - "bei 1024px mit Multi-Skalen-Unterstützung trainiert und liefert " - "die beste Bildqualität. " - "'PixArt-Sigma-XL-2-512-MS' ist die 512px-Variante, schneller und " - "leichter bei dennoch scharfen Ergebnissen." + "Welcher PixArt-Sigma-Checkpoint verwendet wird: '1024' für " + "beste Qualität bei 1024x1024 px oder '512' für ein schnelleres, " + "leichteres Modell bei 512x512 px. Beide Checkpoints werden " + "zusammen heruntergeladen." ), zh=( - "要加载的 PixArt-Sigma 检查点。" - "'PixArt-Sigma-XL-2-1024-MS' 是以 1024px 训练的高分辨率变体," - "支持多尺度,图像质量最佳。" - "'PixArt-Sigma-XL-2-512-MS' 是 512px 变体,速度更快、更轻量," - "同样能产生清晰效果。" + "使用哪个 PixArt-Sigma 检查点:'1024' 表示 1024x1024 " + "像素的最佳质量,'512' 表示 512x512 像素更快更轻量的模型。" + "两个检查点会一起下载。" ), ), alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", + en="Checkpoint", + es="Checkpoint", + pt="Checkpoint", + de="Checkpoint", + zh="检查点", ), ) # type: ignore @@ -376,7 +361,7 @@ class PixArtSigmaSchema(BaseSchema): ) # type: ignore -class PixArtSigmaModel(TextToImageGenerationTaskModel): +class PixArtSigma(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): """Diffusion Transformer model for high efficiency text-to-image generation. Wraps the PixArt-Sigma pipeline, which replaces the U-Net backbone used @@ -398,6 +383,12 @@ class PixArtSigmaModel(TextToImageGenerationTaskModel): """ SCHEMA = PixArtSigmaSchema + # The 1024 checkpoint is a full pipeline (T5, VAE, scheduler, tokenizer). + # The 512 checkpoint ships only a transformer, injected into this pipeline + # when the 512 variant is selected, so both repos are downloaded together. + MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + TRANSFORMER_512_REPO: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + DOWNLOAD_SIZE_BYTES: int = 24280249290 COLOR: str = "#6a1b9a" DISPLAY_NAME: str = MultilingualString( en="PixArt-Sigma", @@ -502,22 +493,14 @@ def __init__(self, **kwargs): num_images_per_prompt : int Number of images to generate per prompt call. """ - import torch - from diffusers import PixArtSigmaPipeline - kwargs = self.validate_and_transform(kwargs) use_gpu = DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ) - self.model = PixArtSigmaPipeline.from_pretrained( - self.model_name, - torch_dtype=torch.float16 if use_gpu else torch.float32, - ).to(self.device) + self.checkpoint = kwargs.get("checkpoint") + self.model = self._build_pipeline(use_gpu).to(self.device) self.negative_prompt = kwargs.get("negative_prompt") self.num_inference_steps = kwargs.get("num_inference_steps") @@ -527,6 +510,57 @@ def __init__(self, **kwargs): self.height = kwargs.get("height") self.num_images_per_prompt = kwargs.get("num_images_per_prompt") + @classmethod + def hf_repos(cls): + """Download both the 1024 (full pipeline) and 512 (transformer) repos. + + Returns + ------- + list of tuple of (str, str) + The 1024 checkpoint (T5, VAE, scheduler, tokenizer, transformer) + and the 512 checkpoint (transformer only), so either variant can be + used after a single download. + """ + return [(cls.MODEL_NAME, "model"), (cls.TRANSFORMER_512_REPO, "model")] + + def _build_pipeline(self, use_gpu: bool): + """Load the PixArt-Sigma pipeline for the selected checkpoint. + + The pipeline scaffold (T5, VAE, scheduler, tokenizer) always comes from + the 1024 repo. For the ``"1024"`` checkpoint its own transformer is + used; for ``"512"`` the transformer from the 512 repo is injected (the + 512 repo has no ``model_index.json`` and cannot be loaded on its own). + + Parameters + ---------- + use_gpu : bool + Whether a GPU is available (selects float16 vs float32). + + Returns + ------- + diffusers.PixArtSigmaPipeline + The loaded pipeline (not yet moved to a device). + """ + import torch + from diffusers import PixArtSigmaPipeline + + dtype = torch.float16 if use_gpu else torch.float32 + pipeline_dir = str(self._repo_dir(self.MODEL_NAME)) + self.model_name = pipeline_dir + + if self.checkpoint == "512": + from diffusers import Transformer2DModel + + transformer_dir = str(self._repo_dir(self.TRANSFORMER_512_REPO)) + transformer = Transformer2DModel.from_pretrained( + transformer_dir, subfolder="transformer", torch_dtype=dtype + ) + return PixArtSigmaPipeline.from_pretrained( + pipeline_dir, transformer=transformer, torch_dtype=dtype + ) + + return PixArtSigmaPipeline.from_pretrained(pipeline_dir, torch_dtype=dtype) + def generate(self, input: str) -> List[Any]: """Generate images from a text prompt. diff --git a/DashAI/back/models/hugging_face/qwen_model.py b/DashAI/back/models/hugging_face/qwen_model.py index 475c536bc..e37706a18 100644 --- a/DashAI/back/models/hugging_face/qwen_model.py +++ b/DashAI/back/models/hugging_face/qwen_model.py @@ -1,468 +1,136 @@ -from typing import List +"""Qwen 2.5 Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -class QwenSchema(BaseSchema): - """Schema for QwenModel hyperparameters. - - Configures the Qwen 2.5 Instruct checkpoint variant (0.5B or 1.5B), generation - length, sampling temperature, frequency penalty, context window, and target - device. The GGUF filename is selected automatically using a Q8_0 quantization - pattern; no manual filename override is exposed. - """ - - model_name: schema_field( - enum_field( - enum=[ - "Qwen/Qwen2.5-0.5B-Instruct-GGUF", - "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - ] - ), - placeholder="Qwen/Qwen2.5-1.5B-Instruct-GGUF", - description=MultilingualString( - en=( - "The Qwen 2.5 Instruct checkpoint to load in GGUF format. " - "'0.5B' (500M parameters) is faster and uses less memory, suitable " - "for lightweight tasks on CPU. '1.5B' (1.5B parameters) is more " - "capable and produces higher-quality responses at the cost of " - "more memory and slightly slower inference." - ), - es=( - "El checkpoint Qwen 2.5 Instruct a cargar en formato GGUF. " - "'0.5B' (500M parámetros) es más rápido y usa menos memoria, " - "adecuado para tareas ligeras en CPU. '1.5B' (1.5B parámetros) " - "es más capaz y produce respuestas de mayor calidad a costa de " - "más memoria e inferencia levemente más lenta." - ), - pt=( - "O checkpoint Qwen 2.5 Instruct a carregar em formato GGUF. " - "'0.5B' (500M parâmetros) é mais rápido e usa menos memória, " - "adequado para tarefas leves em CPU. '1.5B' (1.5B parâmetros) " - "é mais capaz e produz respostas de maior qualidade ao custo de " - "mais memória e inferência levemente mais lenta." - ), - de=( - "Der im GGUF-Format zu ladende Qwen 2.5 Instruct-Checkpoint. " - "'0.5B' (500M Parameter) ist schneller und verbraucht weniger Speicher," - "geeignet für leichte CPU-Aufgaben. '1.5B' (1,5B Parameter) ist " - "leistungsfähiger und liefert qualitativ hochwertigere Antworten " - "auf Kosten von mehr Speicher und etwas langsamerer Inferenz." - ), - zh=( - "要加载的 Qwen 2.5 Instruct GGUF 格式检查点。" - "'0.5B'(5亿参数)速度更快、内存占用更少,适合 CPU 上的轻量级任务。" - "'1.5B'(15亿参数)能力更强,生成质量更高,但需要更多内存且推理速度略慢。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Qwen25_05BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Qwen 2.5 0.5B Instruct GGUF checkpoint (Q8_0 quantization). - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code. Must not " - "exceed the context window minus the prompt length." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas o " - "código. No debe superar la ventana de contexto menos la longitud " - "del prompt." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas ou " - "código. Não deve exceder a janela de contexto menos o comprimento " - "do prompt." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " - "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " - "überschreiten." - ), - zh=( - "模型每次响应生成的最大新 token 数量。" - "大约 1 token 约等于 0.75 个英文单词。短答案设为 100-200," - "详细说明或代码设为 500-1000。不得超过上下文窗口减去提示词长度的值。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore + A compact 500M-parameter instruction-tuned model from Alibaba Cloud, + well suited for lightweight CPU inference. Weights are stored locally + after a one-time download from HuggingFace. - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model always picks the most likely token (greedy, fully " - "deterministic). Around 0.7 is a good balance for conversational " - "tasks. At 1.0 outputs are maximally varied and unpredictable." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad de la salida " - "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " - "probable (greedy, totalmente determinista). Alrededor de 0.7 es " - "un buen equilibrio para tareas conversacionales. En 1.0 las " - "salidas son máximamente variadas e impredecibles." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " - "provável (greedy, totalmente determinístico). Em torno de 0.7 é " - "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " - "saídas são maximamente variadas e imprevisíveis." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " - "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " - "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " - "unvorhersehbar." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" - "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - Qwen Team (2024). "Qwen2.5 Technical Report." + https://arxiv.org/abs/2412.15115 + - https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " - "and the model may repeat itself. Values around 0.1-0.3 gently " - "discourage repetition. High values (1.5+) strongly prevent reuse " - "of any word, which may produce less coherent text." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " - "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " - "suavemente la repetición. Valores altos (1.5+) previenen " - "fuertemente la reutilización de palabras, lo que puede producir " - "texto menos coherente." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base em " - "sua frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o " - "modelo pode se repetir. Valores em torno de 0.1-0.3 desestimulam " - "suavemente a repetição. Valores altos (1.5+) impedem fortemente a " - "reutilização de palavras, o que pode produzir texto menos coerente." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " - "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " - "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " - "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " - "führen kann." - ), - zh=( - "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "0.0 时无惩罚,模型可能重复输出。0.1-0.3 左右可轻微抑制重复。" - "高值(1.5+)会强烈阻止任何词的复用,可能导致文本连贯性下降。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "Qwen/Qwen2.5-0.5B-Instruct-GGUF" + GGUF_PATTERN = "*8_0.gguf" + DOWNLOAD_SIZE_BYTES = 675710816 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#2e7d32" + DISPLAY_NAME = MultilingualString( + en="Qwen2.5 0.5B Instruct", + es="Qwen2.5 0.5B Instruct", + pt="Qwen2.5 0.5B Instruct", + de="Qwen2.5 0.5B Instruct", + zh="Qwen2.5 0.5B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Qwen 2.5 0.5B Instruct is a 500M-parameter instruction-tuned " + "language model by Alibaba Cloud, loaded as a Q8_0 GGUF for " + "efficient CPU inference. It is the fastest and most " + "memory-efficient Qwen 2.5 variant in DashAI, ideal for rapid " + "prototyping or devices with limited RAM. Model available at " + "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=32768), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Larger values allow longer " - "conversations but consume more RAM/VRAM. Qwen 2.5 supports up to " - "32768 tokens natively; keep this at or below that limit." - ), - es=( - "Presupuesto total de tokens para una sola pasada, incluyendo tanto " - "el prompt de entrada como la respuesta generada. Valores más altos " - "permiten conversaciones más largas pero consumen más RAM/VRAM. " - "Qwen 2.5 soporta hasta 32768 tokens de forma nativa; mantenga " - "este valor igual o por debajo de ese límite." - ), - pt=( - "Orçamento total de tokens para uma única passagem, incluindo tanto " - "o prompt de entrada quanto a resposta gerada. Valores maiores " - "permitem conversas mais longas mas consomem mais RAM/VRAM. " - "Qwen 2.5 suporta até 32768 tokens nativamente; mantenha " - "este valor igual ou abaixo desse limite." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " - "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " - "Qwen 2.5 unterstützt nativ bis zu 32768 Token; halten Sie " - "diesen Wert gleich oder unter diesem Limit." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示词和生成的响应。" - "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" - "Qwen 2.5 原生支持最多 32768 个 token,请保持此值不超过该限制。" - ), + es=( + "Qwen 2.5 0.5B Instruct es un modelo de 500M parámetros ajustado " + "para instrucciones por Alibaba Cloud, cargado como GGUF Q8_0 " + "para inferencia eficiente en CPU. Es la variante Qwen 2.5 más " + "rápida y con menor uso de memoria en DashAI, ideal para " + "prototipado rápido o dispositivos con RAM limitada." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Qwen 2.5 0.5B Instruct é um modelo de 500M parâmetros ajustado " + "para instruções pela Alibaba Cloud, carregado como GGUF Q8_0 " + "para inferência eficiente em CPU. É a variante Qwen 2.5 mais " + "rápida e com menor uso de memória no DashAI, ideal para " + "prototipagem rápida ou dispositivos com RAM limitada." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. Selecting a GPU option " - "offloads all layers for faster inference, setting n_gpu_layers=-1 " - "so every transformer layer is GPU-accelerated." - ), - es=( - "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " - "ejecuta el modelo completamente en RAM sin requisito de GPU. " - "Seleccionar una opción de GPU descarga todas las capas para " - "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " - "cada capa del transformer sea acelerada por GPU." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " - "executa o modelo completamente na RAM sem requisito de GPU. " - "Selecionar uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida, definindo n_gpu_layers=-1 para que " - "cada camada do transformer seja acelerada por GPU." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " - "lagert alle Schichten für schnellere Inferenz aus und setzt " - "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." - ), - zh=( - "llama.cpp 推理所使用的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" - "选择 GPU 选项会将所有层卸载以加快推理速度," - "并设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" - ), + de=( + "Qwen 2.5 0.5B Instruct ist ein 500M-Parameter-Instruktionsmodell " + "von Alibaba Cloud, als Q8_0-GGUF für effiziente CPU-Inferenz " + "geladen. Es ist die schnellste und speichereffizienteste " + "Qwen-2.5-Variante in DashAI, ideal für schnelles Prototyping " + "oder Geräte mit begrenztem RAM." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Qwen 2.5 0.5B Instruct 是阿里云推出的 5 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持高效 CPU 推理。" + "这是 DashAI 中速度最快、内存占用最低的 Qwen 2.5 变体," + "非常适合快速原型开发或内存受限的设备。" ), - ) # type: ignore - + ) -class QwenModel(TextToTextGenerationTaskModel): - """Qwen 2.5 Instruct model for efficient text generation via llama.cpp. - Qwen 2.5 is a series of dense transformer language models from Alibaba Cloud, - spanning 0.5B to 72B parameters. The DashAI integration exposes the 0.5B and - 1.5B Instruct variants, which run comfortably on CPU. Both are trained on 18 - trillion tokens with improved coding, mathematics, and multilingual capability - over Qwen 2. +class Qwen25_15BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Qwen 2.5 1.5B Instruct GGUF checkpoint (Q8_0 quantization). - Models are loaded as GGUF Q8_0 quantized checkpoints via ``llama-cpp-python``; - the quantization file is selected automatically from the HuggingFace repo. + A 1.5B-parameter instruction-tuned model from Alibaba Cloud that offers + higher response quality than the 0.5B variant while still running on CPU. + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Qwen Team (2024). "Qwen2.5 Technical Report." https://arxiv.org/abs/2412.15115 - - [2] https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF + - Qwen Team (2024). "Qwen2.5 Technical Report." + https://arxiv.org/abs/2412.15115 + - https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF """ - SCHEMA = QwenSchema + REPO_ID = "Qwen/Qwen2.5-1.5B-Instruct-GGUF" + GGUF_PATTERN = "*8_0.gguf" + DOWNLOAD_SIZE_BYTES = 1894532128 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#2e7d32" - DISPLAY_NAME: str = MultilingualString( - en="Qwen Model", - es="Modelo Qwen", - pt="Modelo Qwen", - de="Qwen-Modell", - zh="Qwen 模型", + DISPLAY_NAME = MultilingualString( + en="Qwen2.5 1.5B Instruct", + es="Qwen2.5 1.5B Instruct", + pt="Qwen2.5 1.5B Instruct", + de="Qwen2.5 1.5B Instruct", + zh="Qwen2.5 1.5B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Qwen 2.5 is an instruction-tuned large language model by Alibaba Cloud, " - "loaded in GGUF format for efficient CPU and GPU inference via the " - "llama.cpp library. It supports multi-turn conversation, reasoning, " - "coding, and general text generation. Available in 0.5B and 1.5B " - "parameter sizes. Models are available at " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF and " + "Qwen 2.5 1.5B Instruct is a 1.5B-parameter instruction-tuned " + "language model by Alibaba Cloud, loaded as a Q8_0 GGUF for " + "efficient CPU inference. It provides stronger reasoning and " + "generation quality than the 0.5B variant at the cost of slightly " + "more memory. Model available at " "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." ), es=( - "Qwen 2.5 es un modelo de lenguaje grande ajustado para instrucciones " - "por Alibaba Cloud, cargado en formato GGUF para inferencia eficiente en " - "CPU y GPU mediante la librería llama.cpp. Soporta conversación " - "multi-turno, razonamiento, programación y generación de texto en " - "general. Disponible en tamaños de 0.5B y 1.5B parámetros. Los modelos " - "están disponibles en " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF y " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct es un modelo de 1.5B parámetros ajustado " + "para instrucciones por Alibaba Cloud, cargado como GGUF Q8_0 " + "para inferencia eficiente en CPU. Ofrece mayor capacidad de " + "razonamiento y calidad de generación que la variante de 0.5B a " + "costa de un poco más de memoria." ), pt=( - "Qwen 2.5 é um modelo de linguagem grande ajustado para instruções " - "pela Alibaba Cloud, carregado em formato GGUF para inferência eficiente " - "em CPU e GPU via biblioteca llama.cpp. Suporta conversa multi-turno, " - "raciocínio, programação e geração de texto em geral. Disponível nos " - "tamanhos 0.5B e 1.5B parâmetros. Os modelos estão disponíveis em " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF e " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct é um modelo de 1.5B parâmetros ajustado " + "para instruções pela Alibaba Cloud, carregado como GGUF Q8_0 " + "para inferência eficiente em CPU. Oferece melhor raciocínio e " + "qualidade de geração que a variante de 0.5B ao custo de um pouco " + "mais de memória." ), de=( - "Qwen 2.5 ist ein instruktionsoptimiertes großes Sprachmodell von " - "Alibaba Cloud, im GGUF-Format für effiziente CPU- und GPU-Inferenz über " - "die llama.cpp-Bibliothek geladen. Unterstützt Mehrfachdialog, " - "Schlussfolgerung, Programmierung und allgemeine Textgenerierung. " - "Verfügbar in den Parametergrößen 0,5B und 1,5B. Modelle verfügbar unter " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF und " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct ist ein 1,5B-Parameter-Instruktionsmodell " + "von Alibaba Cloud, als Q8_0-GGUF für effiziente CPU-Inferenz " + "geladen. Es bietet besseres Schlussfolgern und " + "Generierungsqualität als die 0,5B-Variante, benötigt jedoch " + "etwas mehr Speicher." ), zh=( - "Qwen 2.5 是阿里云开发的指令微调大语言模型," - "以 GGUF 格式加载,通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理、编程和通用文本生成。提供 0.5B 和 1.5B 参数规格。" + "Qwen 2.5 1.5B Instruct 是阿里云推出的 15 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持高效 CPU 推理。" + "与 0.5B 变体相比,它具有更强的推理能力和生成质量,但需要稍多的内存。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Qwen 2.5 Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. The Q8_0 quantization file is always selected regardless - of the chosen model variant. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"Qwen/Qwen2.5-1.5B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. Please install it to use QwenModel." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get("model_name", "Qwen/Qwen2.5-1.5B-Instruct-GGUF") - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = "*8_0.gguf" - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0, - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/roberta_transformer.py b/DashAI/back/models/hugging_face/roberta_transformer.py index 5f65ea004..434a3bfd4 100644 --- a/DashAI/back/models/hugging_face/roberta_transformer.py +++ b/DashAI/back/models/hugging_face/roberta_transformer.py @@ -34,28 +34,29 @@ class RobertaTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Robustly optimised BERT for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT optimizado robustamente para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT otimizado robustamente para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Robust optimiertes BERT für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "经过鲁棒优化的 BERT,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#E65100" ICON: str = "SmartToy" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "roberta-base" + DOWNLOAD_SIZE_BYTES: int = 1003342916 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_roberta" diff --git a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py index 900bfb44f..206aa3d76 100644 --- a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -177,7 +180,7 @@ class SD15DepthControlNetSchema(BaseSchema): ) # type: ignore -def get_depth_map_sd15(image, device): +def get_depth_map_sd15(image, device, model_source="Intel/dpt-hybrid-midas"): """Convert an input image to a normalised depth map for SD 1.5 ControlNet. Uses Intel's DPT-Hybrid-MiDaS model to estimate per-pixel depth, then @@ -205,10 +208,8 @@ def get_depth_map_sd15(image, device): from PIL import Image from transformers import DPTForDepthEstimation, DPTImageProcessor - depth_estimator = DPTForDepthEstimation.from_pretrained( - "Intel/dpt-hybrid-midas" - ).to(device) - feature_extractor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas") + depth_estimator = DPTForDepthEstimation.from_pretrained(model_source).to(device) + feature_extractor = DPTImageProcessor.from_pretrained(model_source) pixel_values = feature_extractor(images=image, return_tensors="pt").pixel_values.to( device @@ -239,7 +240,7 @@ def get_depth_map_sd15(image, device): return image -class SD15DepthControlNetModel(BaseControlNetModel): +class SD15DepthControlNetModel(HFDownloadableMixin, BaseControlNetModel): """Depth-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. A depth map is estimated from the @@ -257,6 +258,12 @@ class SD15DepthControlNetModel(BaseControlNetModel): """ SCHEMA = SD15DepthControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-depth", "model"), + ("Intel/dpt-hybrid-midas", "model"), + ] + DOWNLOAD_SIZE_BYTES = 50640633273 COLOR: str = "#4e342e" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 Depth ControlNet", @@ -349,12 +356,12 @@ def __init__(self, **kwargs: Any): ) controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-depth", + self._local_or_repo("lllyasviel/sd-controlnet-depth"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) @@ -382,7 +389,9 @@ def generate(self, input: Tuple["Image.Image", str]) -> List[Any]: image = input[0] prompt = input[1] - depth_map = get_depth_map_sd15(image, self.device) + depth_map = get_depth_map_sd15( + image, self.device, self._local_or_repo("Intel/dpt-hybrid-midas") + ) output = self.pipe( prompt=prompt, image=depth_map, diff --git a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py index c04273c9b..c1647539f 100644 --- a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -166,7 +169,7 @@ class SD15HEDControlNetSchema(BaseSchema): ) # type: ignore -class SD15HEDControlNetModel(BaseControlNetModel): +class SD15HEDControlNetModel(HFDownloadableMixin, BaseControlNetModel): """HED soft-edge-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. Soft edge maps are extracted from @@ -188,6 +191,12 @@ class SD15HEDControlNetModel(BaseControlNetModel): """ SCHEMA = SD15HEDControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-hed", "model"), + ("lllyasviel/Annotators", "model"), + ] + DOWNLOAD_SIZE_BYTES = 60738022767 COLOR: str = "#006064" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 HED ControlNet", @@ -302,15 +311,17 @@ def __init__(self, **kwargs: Any): f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.hed_detector = HEDdetector.from_pretrained("lllyasviel/Annotators") + self.hed_detector = HEDdetector.from_pretrained( + self._local_or_repo("lllyasviel/Annotators") + ) controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-hed", + self._local_or_repo("lllyasviel/sd-controlnet-hed"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py index 4b575a2a1..6a37aa83e 100644 --- a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -162,7 +165,7 @@ class SD15OpenPoseControlNetSchema(BaseSchema): ) # type: ignore -class SD15OpenPoseControlNetModel(BaseControlNetModel): +class SD15OpenPoseControlNetModel(HFDownloadableMixin, BaseControlNetModel): """OpenPose-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. Human body keypoints and skeleton @@ -183,6 +186,12 @@ class SD15OpenPoseControlNetModel(BaseControlNetModel): """ SCHEMA = SD15OpenPoseControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-openpose", "model"), + ("lllyasviel/Annotators", "model"), + ] + DOWNLOAD_SIZE_BYTES = 60737694276 COLOR: str = "#880e4f" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 OpenPose ControlNet", @@ -295,15 +304,17 @@ def __init__(self, **kwargs: Any): f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.pose_detector = OpenposeDetector.from_pretrained("lllyasviel/Annotators") + self.pose_detector = OpenposeDetector.from_pretrained( + self._local_or_repo("lllyasviel/Annotators") + ) controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-openpose", + self._local_or_repo("lllyasviel/sd-controlnet-openpose"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py index ba0cb3594..83a5bd1d3 100644 --- a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -267,7 +270,7 @@ def get_canny_image( return Image.fromarray(edges_rgb) -class SDXLCannyControlNetModel(BaseControlNetModel): +class SDXLCannyControlNetModel(HFDownloadableMixin, BaseControlNetModel): """Canny-edge-conditioned ControlNet pipeline built on Stable Diffusion XL 1.0. Takes an input image and a text prompt. Canny edge maps are extracted using @@ -288,6 +291,12 @@ class SDXLCannyControlNetModel(BaseControlNetModel): """ SCHEMA = SDXLCannyControlNetSchema + HF_REPOS = [ + ("stabilityai/stable-diffusion-xl-base-1.0", "model"), + ("diffusers/controlnet-canny-sdxl-1.0", "model"), + ("madebyollin/sdxl-vae-fp16-fix", "model"), + ] + DOWNLOAD_SIZE_BYTES = 51644917846 COLOR: str = "#1a237e" DISPLAY_NAME: str = MultilingualString( en="SDXL Canny ControlNet", @@ -402,17 +411,17 @@ def __init__(self, **kwargs: Any): ) controlnet = ControlNetModel.from_pretrained( - "diffusers/controlnet-canny-sdxl-1.0", + self._local_or_repo("diffusers/controlnet-canny-sdxl-1.0"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) vae = AutoencoderKL.from_pretrained( - "madebyollin/sdxl-vae-fp16-fix", + self._local_or_repo("madebyollin/sdxl-vae-fp16-fix"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionXLControlNetPipeline.from_pretrained( - "stabilityai/stable-diffusion-xl-base-1.0", + self._local_or_repo("stabilityai/stable-diffusion-xl-base-1.0"), controlnet=controlnet, vae=vae, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, diff --git a/DashAI/back/models/hugging_face/sdxl_turbo_model.py b/DashAI/back/models/hugging_face/sdxl_turbo_model.py index 918d90aed..0b7c8cca7 100644 --- a/DashAI/back/models/hugging_face/sdxl_turbo_model.py +++ b/DashAI/back/models/hugging_face/sdxl_turbo_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -299,7 +302,7 @@ class SDXLTurboSchema(BaseSchema): ) # type: ignore -class SDXLTurboModel(TextToImageGenerationTaskModel): +class SDXLTurboModel(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): """Distilled SDXL model for near real time text-to-image generation. Wraps ``stabilityai/sdxl-turbo``, a version of Stable Diffusion XL @@ -323,6 +326,9 @@ class SDXLTurboModel(TextToImageGenerationTaskModel): """ SCHEMA = SDXLTurboSchema + MODEL_NAME: str = "stabilityai/sdxl-turbo" + # SDXL-Turbo diffusers pipeline is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 41631892171 COLOR: str = "#b71c1c" DISPLAY_NAME: str = MultilingualString( en="SDXL Turbo", @@ -423,7 +429,7 @@ def __init__(self, **kwargs): ) self.model = StableDiffusionXLPipeline.from_pretrained( - "stabilityai/sdxl-turbo", + self._pretrained_source(None), torch_dtype=torch.float16 if use_gpu else torch.float32, variant="fp16" if use_gpu else None, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/smol_lm_model.py b/DashAI/back/models/hugging_face/smol_lm_model.py index a5f56bdd9..e550a19b9 100644 --- a/DashAI/back/models/hugging_face/smol_lm_model.py +++ b/DashAI/back/models/hugging_face/smol_lm_model.py @@ -1,454 +1,123 @@ -from typing import List +"""SmolLM2 Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -SMOLLM_FILENAME_MAP = { - "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF": "*q4_k_m.gguf", - "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF": "*q8_0.gguf", -} +class SmolLM2_360MInstruct(GGUFTextGenerationModel): # noqa: N801 + """SmolLM2 360M Instruct GGUF checkpoint (Q8_0 quantization). -class SmolLMSchema(BaseSchema): - """Schema for SmolLM2 model hyperparameters. + A very small 360M-parameter instruction-tuned model from HuggingFace, + designed for fast on-device inference. Weights are stored locally after a + one-time download from HuggingFace. - Configures the SmolLM2 Instruct checkpoint variant (360M or 1.7B), generation - length, sampling temperature, frequency penalty, context window, and target - device. The GGUF filename is resolved automatically from ``SMOLLM_FILENAME_MAP``: - Q4_K_M quantization for 1.7B and Q8_0 quantization for 360M. + References + ---------- + - https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF """ - model_name: schema_field( - enum_field( - enum=[ - "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF", - "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF", - ] - ), - placeholder="HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF", - description=MultilingualString( - en=( - "The SmolLM2 Instruct checkpoint to load in GGUF format. " - "'SmolLM2-1.7B' is a 1.7B-parameter instruction model with strong " - "performance for on-device and edge inference. " - "'SmolLM2-360M' is an ultra-compact 360M-parameter model for " - "extremely fast CPU inference with minimal memory usage (~300 MB). " - "Both models are trained on diverse synthetic datasets by Hugging Face." - ), - es=( - "El checkpoint SmolLM2 Instruct a cargar en formato GGUF. " - "'SmolLM2-1.7B' es un modelo de instrucción de 1.7B parámetros con " - "fuerte rendimiento para inferencia en dispositivos y en el borde. " - "'SmolLM2-360M' es un modelo ultra-compacto de 360M parámetros para " - "inferencia CPU extremadamente rápida con uso mínimo de memoria " - "(~300 MB). " - "Ambos modelos son entrenados en datasets sintéticos diversos por " - "Hugging Face." - ), - pt=( - "O checkpoint SmolLM2 Instruct a carregar em formato GGUF. " - "'SmolLM2-1.7B' é um modelo de instrução de 1.7B parâmetros com " - "forte desempenho para inferência em dispositivos e na borda. " - "'SmolLM2-360M' é um modelo ultra-compacto de 360M parâmetros para " - "inferência CPU extremamente rápida com uso mínimo de memória " - "(~300 MB). " - "Ambos os modelos são treinados em conjuntos de dados sintéticos " - "diversos pelo Hugging Face." - ), - de=( - "Der im GGUF-Format zu ladende SmolLM2 Instruct-Checkpoint. " - "'SmolLM2-1.7B' ist ein 1,7B-Parameter-Instruktionsmodell mit starker " - "Leistung für Inferenz auf Endgeräten und Edge-Systemen. " - "'SmolLM2-360M' ist ein ultra-kompaktes 360M-Parameter-Modell für " - "extrem schnelle CPU-Inferenz mit minimalem Speicherbedarf (~300 MB). " - "Beide Modelle werden von Hugging Face auf diversen synthetischen " - "Datensätzen trainiert." - ), - zh=( - "以 GGUF 格式加载的 SmolLM2 Instruct 检查点。" - "'SmolLM2-1.7B' 是 17 亿参数指令模型,适用于端侧和边缘推理。" - "'SmolLM2-360M' 是 3.6 亿参数超紧凑模型,CPU 推理极快," - "内存占用极低(约 300 MB)。" - "两款模型均由 Hugging Face 在多样化合成数据集上训练。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. SmolLM2 models are optimized " - "for short to medium-length responses." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Los modelos " - "SmolLM2 están optimizados para respuestas cortas a medianas." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Os modelos " - "SmolLM2 são otimizados para respostas curtas a médias." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. SmolLM2-Modelle sind " - "für kurze bis mittellange Antworten optimiert." - ), - zh=( - "模型每次响应生成的最大新词元数。" - "约 1 词元 ≈ 0.75 个英文单词。" - "SmolLM2 模型针对短至中等长度的响应进行了优化。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大词元数", - ), - ) # type: ignore - - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 outputs are deterministic. Around 0.7 balances quality and " - "creativity." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 las salidas son deterministas. Alrededor de 0.7 equilibra " - "calidad y creatividad." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 as saídas são determinísticas. " - "Em torno de 0.7 equilibra qualidade e criatividade." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 sind die Ausgaben deterministisch. Um 0.7 balanciert " - "Qualität und Kreativität." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时输出为确定性结果,0.7 左右可平衡质量与创造力。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore - - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desestimulam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据词元在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "较高的值可抑制重复内容。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF" + GGUF_PATTERN = "*q8_0.gguf" + DOWNLOAD_SIZE_BYTES = 386404992 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#00695c" + DISPLAY_NAME = MultilingualString( + en="SmolLM2 360M Instruct", + es="SmolLM2 360M Instruct", + pt="SmolLM2 360M Instruct", + de="SmolLM2 360M Instruct", + zh="SmolLM2 360M Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "SmolLM2 360M Instruct is a compact 360M-parameter instruction-tuned " + "language model from HuggingFace, loaded as a Q8_0 GGUF for very fast " + "CPU inference. It is the lightest text-generation model in DashAI, " + "ideal for constrained devices. Model available at " + "https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=8192), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. SmolLM2 models support " - "up to 8K tokens natively." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Los modelos SmolLM2 soportan hasta 8K tokens de " - "forma nativa." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Os modelos SmolLM2 suportam até 8K tokens nativamente." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und Antwort. " - "SmolLM2-Modelle unterstützen nativ bis zu 8K Token." - ), - zh=( - "单次前向传播的词元总预算,包含输入提示和生成响应。" - "SmolLM2 模型原生支持最多 8K 词元。" - ), + es=( + "SmolLM2 360M Instruct es un modelo de lenguaje compacto de 360M " + "parametros ajustado para instrucciones por HuggingFace, cargado como " + "GGUF Q8_0 para inferencia muy rapida en CPU. Es el modelo de generacion " + "de texto mas ligero de DashAI, ideal para dispositivos limitados." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "SmolLM2 360M Instruct e um modelo de linguagem compacto de 360M " + "parametros ajustado para instrucoes pela HuggingFace, carregado como " + "GGUF Q8_0 para inferencia muito rapida em CPU. E o modelo de geracao de " + "texto mais leve do DashAI, ideal para dispositivos limitados." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. SmolLM2 models are small " - "enough to run efficiently on CPU even on modest hardware." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM sin requisito de GPU. Los modelos SmolLM2 son lo " - "suficientemente pequeños para ejecutarse eficientemente en CPU " - "incluso " - "en hardware modesto." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo na RAM sem requisito de GPU. Os modelos SmolLM2 são " - "pequenos o suficiente para rodar eficientemente em CPU " - "mesmo em hardware modesto." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "im RAM ohne GPU-Anforderung aus. SmolLM2-Modelle sind klein genug, " - "um auch auf bescheidener Hardware effizient auf der CPU zu laufen." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 将模型完全加载至内存运行," - "无需 GPU。" - "SmolLM2 模型体积小巧,即使在普通硬件上也能高效地在 CPU 上运行。" - ), + de=( + "SmolLM2 360M Instruct ist ein kompaktes 360M-Parameter-Instruktionsmodell " + "von HuggingFace, als Q8_0-GGUF fuer sehr schnelle CPU-Inferenz geladen. " + "Es ist das leichteste Textgenerierungsmodell in DashAI, ideal fuer " + "eingeschraenkte Geraete." ), - alias=MultilingualString( - en="Device", es="Dispositivo", pt="Dispositivo", de="Gerät", zh="设备" + zh=( + "SmolLM2 360M Instruct 是 HuggingFace 推出的 3.6 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持极快的 CPU 推理。" + "这是 DashAI 中最轻量的文本生成模型,非常适合资源受限的设备。" ), - ) # type: ignore - - -class SmolLMModel(TextToTextGenerationTaskModel): - """SmolLM2 Instruct model for on-device text generation via llama.cpp. + ) - SmolLM2 is a family of compact, instruction-tuned language models developed by - Hugging Face TB, designed for efficient on-device and edge deployment. Unlike - larger language models, SmolLM2 achieves competitive benchmark results at very - small parameter counts by training on high-quality synthetic datasets including - cosmopedia-v2, FineWeb-Edu, and StackEdu. - The DashAI integration exposes the 360M and 1.7B Instruct variants. The 360M - model requires under 300 MB of RAM and runs comfortably on modest CPU hardware; - the 1.7B model delivers higher-quality responses while remaining deployable - without a GPU. +class SmolLM2_17BInstruct(GGUFTextGenerationModel): # noqa: N801 + """SmolLM2 1.7B Instruct GGUF checkpoint (Q4_K_M quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``. The - quantization level is variant-dependent: Q8_0 for 360M (higher fidelity at small - size) and Q4_K_M for 1.7B (balanced quality/size trade-off). The filename is - resolved automatically from ``SMOLLM_FILENAME_MAP``. + A 1.7B-parameter instruction-tuned model from HuggingFace offering stronger + generation quality than the 360M variant. Weights are stored locally after a + one-time download from HuggingFace. References ---------- - - [1] Allal, L.B. et al. (2024). "SmolLM2: with great data, comes great - performance." Hugging Face Blog. - https://huggingface.co/blog/smollm2 - - [2] https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF - - [3] https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF + - https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF """ - SCHEMA = SmolLMSchema + REPO_ID = "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF" + GGUF_PATTERN = "*q4_k_m.gguf" + DOWNLOAD_SIZE_BYTES = 1055609536 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#00695c" - DISPLAY_NAME: str = MultilingualString( - en="SmolLM Model", - es="Modelo SmolLM", - pt="Modelo SmolLM", - de="SmolLM-Modell", - zh="SmolLM 模型", + DISPLAY_NAME = MultilingualString( + en="SmolLM2 1.7B Instruct", + es="SmolLM2 1.7B Instruct", + pt="SmolLM2 1.7B Instruct", + de="SmolLM2 1.7B Instruct", + zh="SmolLM2 1.7B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "SmolLM2 is a family of compact instruction-tuned language models by " - "Hugging Face, loaded in GGUF format for efficient CPU and GPU inference " - "via the llama.cpp library. Designed for on-device and edge deployment, " - "SmolLM2 achieves strong benchmark results at very small parameter counts. " - "The 360M variant requires less than 300 MB of RAM, making it ideal for " - "resource-constrained environments. Available in 360M and 1.7B variants. " - "Models available at https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct is a 1.7B-parameter instruction-tuned language " + "model from HuggingFace, loaded as a Q4_K_M GGUF for efficient CPU " + "inference. It provides better reasoning and generation quality than the " + "360M variant. Model available at " + "https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF." ), es=( - "SmolLM2 es una familia de modelos de lenguaje compactos ajustados para " - "instrucciones por Hugging Face, cargados en formato GGUF para inferencia " - "eficiente en CPU y GPU mediante llama.cpp. Diseñados para despliegue en " - "dispositivo y en el borde, SmolLM2 logra fuertes resultados de benchmark " - "con muy pocos parámetros. La variante de 360M requiere menos de 300 MB de " - "RAM, ideal para entornos con recursos limitados. Disponible en variantes " - "de 360M y 1.7B. Modelos en https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct es un modelo de lenguaje de 1.7B parametros " + "ajustado para instrucciones por HuggingFace, cargado como GGUF Q4_K_M " + "para inferencia eficiente en CPU. Ofrece mejor razonamiento y calidad de " + "generacion que la variante de 360M." ), pt=( - "SmolLM2 é uma família de modelos de linguagem compactos ajustados para " - "instruções pelo Hugging Face, carregados em formato GGUF para inferência " - "eficiente em CPU e GPU via llama.cpp. Projetados para implantação em " - "dispositivos e na borda, SmolLM2 alcança fortes resultados de benchmark " - "com pouquíssimos parâmetros. A variante de 360M requer menos de 300 MB de " - "RAM, ideal para ambientes com recursos limitados. " - "Disponível nas variantes " - "360M e 1.7B. Modelos disponíveis em https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct e um modelo de linguagem de 1.7B parametros " + "ajustado para instrucoes pela HuggingFace, carregado como GGUF Q4_K_M " + "para inferencia eficiente em CPU. Oferece melhor raciocinio e qualidade " + "de geracao do que a variante de 360M." ), de=( - "SmolLM2 ist eine Familie kompakter instruktionsoptimierter Sprachmodelle " - "von Hugging Face, im GGUF-Format für effiziente CPU- und GPU-Inferenz " - "über llama.cpp geladen. Für Deployment auf Endgeräten und Edge-Systemen " - "konzipiert, erzielt SmolLM2 starke Benchmark-Ergebnisse mit sehr wenigen " - "Parametern. Die 360M-Variante benötigt weniger als 300 MB RAM und ist " - "ideal für ressourcenbeschränkte Umgebungen. Verfügbar in den Varianten " - "360M und 1,7B. Modelle unter https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct ist ein 1,7B-Parameter-Instruktionsmodell von " + "HuggingFace, als Q4_K_M-GGUF fuer effiziente CPU-Inferenz geladen. " + "Es bietet besseres Schlussfolgern und Generierungsqualitaet als die " + "360M-Variante." ), zh=( - "SmolLM2 是 Hugging Face 推出的紧凑型指令微调语言模型系列," - "以 GGUF 格式加载,通过 llama.cpp 库高效推理。" - "专为端侧和边缘部署设计,提供 360M 和 1.7B 两种规格。" + "SmolLM2 1.7B Instruct 是 HuggingFace 推出的 17 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效 CPU 推理。" + "与 360M 变体相比,它具有更强的推理能力和生成质量。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a SmolLM2 Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. The GGUF filename is resolved from ``SMOLLM_FILENAME_MAP`` - (Q4_K_M for 1.7B, Q8_0 for 360M). - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = SMOLLM_FILENAME_MAP.get(self.model_name, "*q4_k_m.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py index 1482b1da5..03dec56b8 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -153,7 +156,7 @@ class StableDiffusionXLV1ControlNetSchema(BaseSchema): ) # type: ignore -def get_depth_map(image, device): +def get_depth_map(image, device, model_source="Intel/dpt-hybrid-midas"): """Convert an input image to a normalised depth map for SDXL ControlNet. Uses Intel's DPT-Hybrid-MiDaS model to estimate per-pixel depth, then @@ -179,10 +182,8 @@ def get_depth_map(image, device): from PIL import Image from transformers import DPTForDepthEstimation, DPTImageProcessor - depth_estimator = DPTForDepthEstimation.from_pretrained( - "Intel/dpt-hybrid-midas" - ).to(device) - feature_extractor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas") + depth_estimator = DPTForDepthEstimation.from_pretrained(model_source).to(device) + feature_extractor = DPTImageProcessor.from_pretrained(model_source) image = feature_extractor(images=image, return_tensors="pt").pixel_values.to(device) @@ -205,11 +206,18 @@ def get_depth_map(image, device): return image -class StableDiffusionXLV1ControlNet(BaseControlNetModel): +class StableDiffusionXLV1ControlNet(HFDownloadableMixin, BaseControlNetModel): """A wrapper implementation of ControlNet with depth preprocessing and stable diffusion xl 1.0 as pipeline.""" SCHEMA = StableDiffusionXLV1ControlNetSchema + HF_REPOS = [ + ("stabilityai/stable-diffusion-xl-base-1.0", "model"), + ("diffusers/controlnet-depth-sdxl-1.0-small", "model"), + ("madebyollin/sdxl-vae-fp16-fix", "model"), + ("Intel/dpt-hybrid-midas", "model"), + ] + DOWNLOAD_SIZE_BYTES = 39033373420 COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL V1 ControlNet", @@ -313,19 +321,19 @@ def __init__(self, **kwargs: Any): ) self.controlnet = ControlNetModel.from_pretrained( - "diffusers/controlnet-depth-sdxl-1.0-small", + self._local_or_repo("diffusers/controlnet-depth-sdxl-1.0-small"), variant="fp16", use_safetensors=True, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.vae = AutoencoderKL.from_pretrained( - "madebyollin/sdxl-vae-fp16-fix", + self._local_or_repo("madebyollin/sdxl-vae-fp16-fix"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionXLControlNetPipeline.from_pretrained( - "stabilityai/stable-diffusion-xl-base-1.0", + self._local_or_repo("stabilityai/stable-diffusion-xl-base-1.0"), controlnet=self.controlnet, vae=self.vae, variant="fp16", @@ -355,7 +363,9 @@ def generate(self, input: Tuple[Any, str]) -> List[Any]: image = input[0] prompt = input[1] - depth_map = get_depth_map(image, self.device) + depth_map = get_depth_map( + image, self.device, self._local_or_repo("Intel/dpt-hybrid-midas") + ) image = self.pipe( prompt=prompt, image=depth_map, diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py index 8354499f2..3ed411604 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,65 +29,6 @@ class StableDiffusionSchema(BaseSchema): ``StableDiffusionV2Model``. """ - model_name: schema_field( - enum_field( - enum=[ - "sd2-community/stable-diffusion-2", - "sd2-community/stable-diffusion-2-base", - "sd2-community/stable-diffusion-2-1", - "sd2-community/stable-diffusion-2-1-base", - ] - ), - placeholder="sd2-community/stable-diffusion-2", - description=MultilingualString( - en=( - "The specific Stable Diffusion 2.x checkpoint to load. " - "The '-base' variants are trained at 512x512 px and are faster; " - "the nonbase variants target 768x768 px and produce sharper detail. " - "The '2-1' variants are fine-tuned further " - "and generally outperform '2'." - ), - es=( - "El checkpoint específico de Stable Diffusion 2.x a cargar. " - "Las variantes '-base' se entrenan a 512x512 px y son más rápidas; " - "las variantes sin '-base' apuntan a 768x768 px " - "y producen mayor detalle. " - "Las variantes '2-1' están más ajustadas " - "y generalmente superan a '2'." - ), - pt=( - "O checkpoint específico do Stable Diffusion 2.x a carregar. " - "As variantes '-base' são treinadas a 512x512 px e são mais rápidas; " - "as variantes sem '-base' visam 768x768 px " - "e produzem maior detalhe. " - "As variantes '2-1' são mais ajustadas " - "e geralmente superam a '2'." - ), - de=( - "Der zu ladende spezifische Stable Diffusion 2.x-Checkpoint. " - "Die '-base'-Varianten werden bei 512x512 px trainiert und sind " - "schneller; " - "die Nicht-base-Varianten zielen auf 768x768 px ab " - "und liefern schärfere Details. " - "Die '2-1'-Varianten sind weiter feinabgestimmt " - "und übertreffen '2' in der Regel." - ), - zh=( - "要加载的 Stable Diffusion 2.x 检查点。" - "'-base' 变体在 512x512 像素下训练,速度更快;" - "非 base 变体目标分辨率为 768x768 像素,细节更清晰。" - "'2-1' 变体经过进一步微调,通常优于 '2'。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -403,7 +347,9 @@ class StableDiffusionSchema(BaseSchema): ) # type: ignore -class StableDiffusionV2Model(TextToImageGenerationTaskModel): +class StableDiffusion2GenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Latent diffusion model for high resolution text-to-image generation. Wraps the Stable Diffusion 2.x family of checkpoints released by @@ -431,6 +377,7 @@ class StableDiffusionV2Model(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionSchema + MODEL_NAME: str = "" COLOR: str = "#1565c0" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion V2", @@ -538,7 +485,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get("model_name", "sd2-community/stable-diffusion-2") + self.model_name = self._pretrained_source(None) self.model = DiffusionPipeline.from_pretrained( self.model_name, @@ -589,3 +536,233 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusion2(StableDiffusion2GenerationModel): + """768px Stable Diffusion 2 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 25911933905 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2", + es="Stable Diffusion 2", + pt="Stable Diffusion 2", + de="Stable Diffusion 2", + zh="Stable Diffusion 2", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 2 by Stability AI, a latent text-to-image diffusion " + "model conditioned on OpenCLIP text embeddings. This checkpoint is " + "trained at 768x768 px and produces sharp, high-detail images. Weights " + "are downloaded into the component's own folder from the sd2-community " + "mirror. Model page: https://huggingface.co/sd2-community/stable-diffusio" + "n-2" + ), + es=( + "Stable Diffusion 2 de Stability AI, un modelo de difusión latente de " + "texto a imagen condicionado en embeddings de texto OpenCLIP. Este " + "checkpoint se entrena a 768x768 px y produce imágenes nítidas y muy " + "detalladas. Los pesos se descargan en la carpeta propia del componente " + "desde el espejo sd2-community. Página del modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2" + ), + pt=( + "Stable Diffusion 2 da Stability AI, um modelo de difusão latente de " + "texto para imagem condicionado em embeddings de texto OpenCLIP. Este " + "checkpoint é treinado a 768x768 px e produz imagens nítidas e com " + "muitos detalhes. Os pesos são baixados na pasta própria do componente " + "a partir do espelho sd2-community. Página do modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2" + ), + de=( + "Stable Diffusion 2 von Stability AI, ein latentes " + "Text-zu-Bild-Diffusionsmodell, das auf OpenCLIP-Texteinbettungen " + "konditioniert ist. Dieser Checkpoint wird bei 768x768 px trainiert und " + "erzeugt scharfe, detailreiche Bilder. Die Gewichte werden aus dem " + "sd2-community-Spiegel in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/sd2-community/stabl" + "e-diffusion-2" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2,是一种以 OpenCLIP " + "文本嵌入为条件的潜在文本到图像扩散模型。该检查点在 768x768 " + "像素下训练,可生成清晰且细节丰富的图像。权重会从 sd2-community " + "镜像下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/sd2-c" + "ommunity/stable-diffusion-2" + ), + ) + + +class StableDiffusion2_512(StableDiffusion2GenerationModel): # noqa: N801 + """512px base Stable Diffusion 2 checkpoint (faster). + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-base" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 25911843836 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2 (512px)", + es="Stable Diffusion 2 (512px)", + pt="Stable Diffusion 2 (512px)", + de="Stable Diffusion 2 (512px)", + zh="Stable Diffusion 2 (512px)", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 2 base checkpoint by Stability AI, trained at 512x512 " + "px. It is faster and uses less memory than the 768 px variant, making " + "it a good choice for rapid prototyping. Weights are downloaded into the " + "component's own folder from the sd2-community mirror. Model page: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + es=( + "Checkpoint base de Stable Diffusion 2 de Stability AI, entrenado a " + "512x512 px. Es más rápido y usa menos memoria que la variante de 768 " + "px, ideal para prototipado rápido. Los pesos se descargan en la " + "carpeta propia del componente desde el espejo sd2-community. Página " + "del modelo: https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + pt=( + "Checkpoint base do Stable Diffusion 2 da Stability AI, treinado a " + "512x512 px. É mais rápido e usa menos memória que a variante de 768 " + "px, ideal para prototipagem rápida. Os pesos são baixados na pasta " + "própria do componente a partir do espelho sd2-community. Página do " + "modelo: https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + de=( + "Stable Diffusion 2 Basis-Checkpoint von Stability AI, trainiert bei " + "512x512 px. Er ist schneller und benötigt weniger Speicher als die " + "768-px-Variante und eignet sich gut für schnelles Prototyping. Die " + "Gewichte werden aus dem sd2-community-Spiegel in den eigenen Ordner der " + "Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2 基础检查点,在 512x512 " + "像素下训练。相比 768 像素变体速度更快、显存占用更低,非常适合快速原型设" + "计。权重会从 sd2-community 镜像下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + ) + + +class StableDiffusion21(StableDiffusion2GenerationModel): + """768px Stable Diffusion 2.1 checkpoint (further fine-tuned). + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-1" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 36341303572 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2.1", + es="Stable Diffusion 2.1", + pt="Stable Diffusion 2.1", + de="Stable Diffusion 2.1", + zh="Stable Diffusion 2.1", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 2.1 by Stability AI, a further fine-tuned revision of " + "the 2.x family trained at 768x768 px. It generally produces cleaner, " + "more coherent images than the original 2.0. Weights are downloaded into " + "the component's own folder from the sd2-community mirror. Model page: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + es=( + "Stable Diffusion 2.1 de Stability AI, una revisión más ajustada de la " + "familia 2.x entrenada a 768x768 px. Suele producir imágenes más " + "limpias y coherentes que la 2.0 original. Los pesos se descargan en la " + "carpeta propia del componente desde el espejo sd2-community. Página " + "del modelo: https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + pt=( + "Stable Diffusion 2.1 da Stability AI, uma revisão mais ajustada da " + "família 2.x treinada a 768x768 px. Costuma produzir imagens mais " + "limpas e coerentes que a 2.0 original. Os pesos são baixados na pasta " + "própria do componente a partir do espelho sd2-community. Página do " + "modelo: https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + de=( + "Stable Diffusion 2.1 von Stability AI, eine weiter feinabgestimmte " + "Überarbeitung der 2.x-Familie, trainiert bei 768x768 px. Sie erzeugt " + "in der Regel sauberere, kohärentere Bilder als die ursprüngliche 2.0. " + "Die Gewichte werden aus dem sd2-community-Spiegel in den eigenen Ordner " + "der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2.1,是 2.x " + "系列的进一步微调版本,在 768x768 像素下训练。通常比原始的 2.0 " + "生成更干净、更连贯的图像。权重会从 sd2-community " + "镜像下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/sd2-c" + "ommunity/stable-diffusion-2-1" + ), + ) + + +class StableDiffusion21_512(StableDiffusion2GenerationModel): # noqa: N801 + """512px base Stable Diffusion 2.1 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-1-base" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 36341275775 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2.1 (512px)", + es="Stable Diffusion 2.1 (512px)", + pt="Stable Diffusion 2.1 (512px)", + de="Stable Diffusion 2.1 (512px)", + zh="Stable Diffusion 2.1 (512px)", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 2.1 base checkpoint by Stability AI, trained at " + "512x512 px. It combines the 2.1 fine-tuning improvements with the lower " + "memory footprint and faster generation of the 512 px base models. " + "Weights are downloaded into the component's own folder from the " + "sd2-community mirror. Model page: https://huggingface.co/sd2-community/s" + "table-diffusion-2-1-base" + ), + es=( + "Checkpoint base de Stable Diffusion 2.1 de Stability AI, entrenado a " + "512x512 px. Combina las mejoras de ajuste de la 2.1 con el menor " + "consumo de memoria y la generación más rápida de los modelos base de " + "512 px. Los pesos se descargan en la carpeta propia del componente " + "desde el espejo sd2-community. Página del modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), + pt=( + "Checkpoint base do Stable Diffusion 2.1 da Stability AI, treinado a " + "512x512 px. Combina as melhorias de ajuste da 2.1 com o menor uso de " + "memória e a geração mais rápida dos modelos base de 512 px. Os " + "pesos são baixados na pasta própria do componente a partir do espelho " + "sd2-community. Página do modelo: https://huggingface.co/sd2-community/s" + "table-diffusion-2-1-base" + ), + de=( + "Stable Diffusion 2.1 Basis-Checkpoint von Stability AI, trainiert bei " + "512x512 px. Er verbindet die Feinabstimmungs-Verbesserungen von 2.1 mit " + "dem geringeren Speicherbedarf und der schnelleren Generierung der " + "512-px-Basismodelle. Die Gewichte werden aus dem sd2-community-Spiegel " + "in den eigenen Ordner der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2.1 基础检查点,在 512x512 " + "像素下训练。它将 2.1 的微调改进与 512 " + "像素基础模型更低的显存占用和更快的生成速度相结合。权重会从 " + "sd2-community 镜像下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), + ) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py index 34685fd51..ca8daef20 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,67 +29,6 @@ class StableDiffusionSchema(BaseSchema): (``num_images_per_prompt``) for ``StableDiffusionV3Model``. """ - model_name: schema_field( - enum_field( - enum=[ - "stabilityai/stable-diffusion-3-medium-diffusers", - "stabilityai/stable-diffusion-3.5-medium", - "stabilityai/stable-diffusion-3.5-large", - "stabilityai/stable-diffusion-3.5-large-turbo", - ] - ), - placeholder="stabilityai/stable-diffusion-3-medium-diffusers", - description=MultilingualString( - en=( - "The SD3/SD3.5 checkpoint to load. 'sd-3-medium' is the baseline " - "2B-parameter model. 'sd-3.5-medium' improves quality at similar " - "speed. 'sd-3.5-large' (8B) delivers the highest quality but needs " - "more VRAM. 'sd-3.5-large-turbo' is a distilled large model that " - "requires far fewer steps (4-8) for fast high-quality generation. " - "All variants target 1024x1024 px natively." - ), - es=( - "El checkpoint SD3/SD3.5 a cargar. 'sd-3-medium' es el modelo base " - "de 2B parámetros. 'sd-3.5-medium' mejora la calidad a velocidad " - "similar. 'sd-3.5-large' (8B) ofrece la mayor calidad pero necesita " - "más VRAM. 'sd-3.5-large-turbo' es un modelo large destilado que " - "requiere muchos menos pasos (4-8) para generación rápida de alta " - "calidad. Todas las variantes apuntan a 1024x1024 px de forma nativa." - ), - pt=( - "O checkpoint SD3/SD3.5 a carregar. 'sd-3-medium' é o modelo base " - "de 2B parâmetros. 'sd-3.5-medium' melhora a qualidade a velocidade " - "similar. 'sd-3.5-large' (8B) oferece a maior qualidade mas precisa " - "de mais VRAM. 'sd-3.5-large-turbo' é um modelo large destilado que " - "requer muito menos passos (4-8) para geração rápida de alta " - "qualidade. " - "Todas as variantes visam 1024x1024 px nativamente." - ), - de=( - "Der zu ladende SD3/SD3.5-Checkpoint. 'sd-3-medium' ist das " - "2B-Parameter-Basismodell. 'sd-3.5-medium' verbessert die Qualität " - "bei ähnlicher Geschwindigkeit. 'sd-3.5-large' (8B) liefert die höchste" - "Qualität, benötigt aber mehr VRAM. 'sd-3.5-large-turbo' ist ein " - "destilliertes Large-Modell, das deutlich weniger Schritte (4-8) für " - "schnelle hochwertige Generierung benötigt. " - "Alle Varianten zielen nativ auf 1024x1024 px ab." - ), - zh=( - "要加载的 SD3/SD3.5 检查点。'sd-3-medium' 是 2B 参数基准模型。" - "'sd-3.5-medium' 以相近速度提升质量。'sd-3.5-large'(8B)质量最高但" - "需要更多显存。'sd-3.5-large-turbo' 是蒸馏版大模型,仅需 4-8 步即可" - "快速生成高质量图像。所有变体原生目标分辨率为 1024x1024 像素。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - huggingface_key: schema_field( string_field(), placeholder="", @@ -454,7 +396,9 @@ class StableDiffusionSchema(BaseSchema): ) # type: ignore -class StableDiffusionV3Model(TextToImageGenerationTaskModel): +class StableDiffusion3GenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Multimodal Diffusion Transformer model for high-quality text-to-image generation. Wraps the Stable Diffusion 3 and 3.5 family of checkpoints from @@ -477,6 +421,7 @@ class StableDiffusionV3Model(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionSchema + MODEL_NAME: str = "" COLOR: str = "#6a1b9a" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion V3", @@ -544,9 +489,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "stabilityai/stable-diffusion-3-medium-diffusers" - ) + self.model_name = self._pretrained_source(None) self.huggingface_key = kwargs.get("huggingface_key") if self.huggingface_key: @@ -613,3 +556,267 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusion3Medium(StableDiffusion3GenerationModel): + """Stable Diffusion 3 Medium checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3-medium-diffusers" + DOWNLOAD_SIZE_BYTES: int = 31012147557 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3 Medium", + es="Stable Diffusion 3 Medium", + pt="Stable Diffusion 3 Medium", + de="Stable Diffusion 3 Medium", + zh="Stable Diffusion 3 Medium", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 3 Medium by Stability AI, built on the Multimodal " + "Diffusion Transformer (MMDiT) architecture with markedly improved text " + "rendering and prompt adherence over SD2. This is a gated Hugging Face " + "repo, so downloading requires prior authentication with an access " + "token. Weights are downloaded into the component's own folder. Model " + "page: https://huggingface.co/stabilityai/stable-diffusion-3-medium-diffu" + "sers" + ), + es=( + "Stable Diffusion 3 Medium de Stability AI, construido sobre la " + "arquitectura Multimodal Diffusion Transformer (MMDiT) con una " + "representación de texto y adherencia al prompt notablemente mejores " + "que SD2. Es un repositorio restringido de Hugging Face, por lo que la " + "descarga requiere autenticación previa con un token de acceso. Los " + "pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/stabilityai/stable-diffusion-3-medium-dif" + "fusers" + ), + pt=( + "Stable Diffusion 3 Medium da Stability AI, construído sobre a " + "arquitetura Multimodal Diffusion Transformer (MMDiT) com renderização " + "de texto e aderência ao prompt bem melhores que o SD2. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3-medium-diffusers" + ), + de=( + "Stable Diffusion 3 Medium von Stability AI, basierend auf der " + "Multimodal-Diffusion-Transformer-Architektur (MMDiT) mit deutlich " + "verbesserter Textwiedergabe und Prompt-Treue gegenüber SD2. Dies ist " + "ein zugangsbeschränktes Hugging-Face-Repository, daher erfordert der " + "Download eine vorherige Authentifizierung mit einem Zugriffstoken. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-3-mediu" + "m-diffusers" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3 Medium,基于多模态扩散 " + "Transformer(MMDiT)架构,相比 SD2 " + "在文本渲染和提示词遵循方面有显著提升。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3-medium-diffusers" + ), + ) + + +class StableDiffusion35Medium(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Medium checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-medium" + DOWNLOAD_SIZE_BYTES: int = 48861581303 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Medium", + es="Stable Diffusion 3.5 Medium", + pt="Stable Diffusion 3.5 Medium", + de="Stable Diffusion 3.5 Medium", + zh="Stable Diffusion 3.5 Medium", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 3.5 Medium by Stability AI, an updated MMDiT model " + "that balances image quality against hardware requirements, running " + "comfortably on consumer GPUs. This is a gated Hugging Face repo, so " + "downloading requires prior authentication with an access token. Weights " + "are downloaded into the component's own folder. Model page: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-medium" + ), + es=( + "Stable Diffusion 3.5 Medium de Stability AI, un modelo MMDiT " + "actualizado que equilibra la calidad de imagen con los requisitos de " + "hardware y funciona bien en GPUs de consumo. Es un repositorio " + "restringido de Hugging Face, por lo que la descarga requiere " + "autenticación previa con un token de acceso. Los pesos se descargan en " + "la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-medium" + ), + pt=( + "Stable Diffusion 3.5 Medium da Stability AI, um modelo MMDiT atualizado " + "que equilibra a qualidade da imagem com os requisitos de hardware e " + "roda bem em GPUs de consumo. É um repositório restrito do Hugging " + "Face, portanto o download requer autenticação prévia com um token de " + "acesso. Os pesos são baixados na pasta própria do componente. Página " + "do modelo: https://huggingface.co/stabilityai/stable-diffusion-3.5-mediu" + "m" + ), + de=( + "Stable Diffusion 3.5 Medium von Stability AI, ein aktualisiertes " + "MMDiT-Modell, das Bildqualität und Hardwareanforderungen ausbalanciert " + "und komfortabel auf Consumer-GPUs läuft. Dies ist ein " + "zugangsbeschränktes Hugging-Face-Repository, daher erfordert der " + "Download eine vorherige Authentifizierung mit einem Zugriffstoken. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-3.5-med" + "ium" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Medium,是更新的 MMDiT " + "模型,在图像质量与硬件需求之间取得平衡,可在消费级 GPU " + "上流畅运行。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-medium" + ), + ) + + +class StableDiffusion35Large(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Large checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large" + DOWNLOAD_SIZE_BYTES: int = 71585723216 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Large", + es="Stable Diffusion 3.5 Large", + pt="Stable Diffusion 3.5 Large", + de="Stable Diffusion 3.5 Large", + zh="Stable Diffusion 3.5 Large", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 3.5 Large by Stability AI, the highest quality MMDiT " + "model in the 3.5 family, offering the strongest detail and prompt " + "adherence at the cost of more memory and slower generation. This is a " + "gated Hugging Face repo, so downloading requires prior authentication " + "with an access token. Weights are downloaded into the component's own " + "folder. Model page: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large" + ), + es=( + "Stable Diffusion 3.5 Large de Stability AI, el modelo MMDiT de mayor " + "calidad de la familia 3.5, con el mejor detalle y adherencia al prompt " + "a costa de más memoria y una generación más lenta. Es un repositorio " + "restringido de Hugging Face, por lo que la descarga requiere " + "autenticación previa con un token de acceso. Los pesos se descargan en " + "la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + pt=( + "Stable Diffusion 3.5 Large da Stability AI, o modelo MMDiT de maior " + "qualidade da família 3.5, oferecendo o melhor detalhe e aderência ao " + "prompt ao custo de mais memória e geração mais lenta. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + de=( + "Stable Diffusion 3.5 Large von Stability AI, das qualitativ " + "hochwertigste MMDiT-Modell der 3.5-Familie, das beste Detailtreue und " + "Prompt-Treue bietet, allerdings auf Kosten von mehr Speicher und " + "langsamerer Generierung. Dies ist ein zugangsbeschränktes " + "Hugging-Face-Repository, daher erfordert der Download eine vorherige " + "Authentifizierung mit einem Zugriffstoken. Die Gewichte werden in den " + "eigenen Ordner der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Large,是 3.5 系列中质量最高的 " + "MMDiT 模型,提供最强的细节和提示词遵循能力,代价是更高的显存占用和更慢的" + "生成速度。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large" + ), + ) + + +class StableDiffusion35LargeTurbo(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Large Turbo checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large-turbo" + DOWNLOAD_SIZE_BYTES: int = 71582971259 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Large Turbo", + es="Stable Diffusion 3.5 Large Turbo", + pt="Stable Diffusion 3.5 Large Turbo", + de="Stable Diffusion 3.5 Large Turbo", + zh="Stable Diffusion 3.5 Large Turbo", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion 3.5 Large Turbo by Stability AI, a distilled version " + "of 3.5 Large that produces high quality images in only a handful of " + "denoising steps for much faster generation. This is a gated Hugging " + "Face repo, so downloading requires prior authentication with an access " + "token. Weights are downloaded into the component's own folder. Model " + "page: https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turb" + "o" + ), + es=( + "Stable Diffusion 3.5 Large Turbo de Stability AI, una versión " + "destilada de 3.5 Large que produce imágenes de alta calidad en apenas " + "unos pocos pasos de denoising para una generación mucho más rápida. " + "Es un repositorio restringido de Hugging Face, por lo que la descarga " + "requiere autenticación previa con un token de acceso. Los pesos se " + "descargan en la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo" + ), + pt=( + "Stable Diffusion 3.5 Large Turbo da Stability AI, uma versão destilada " + "do 3.5 Large que produz imagens de alta qualidade em apenas alguns " + "passos de denoising para uma geração muito mais rápida. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo" + ), + de=( + "Stable Diffusion 3.5 Large Turbo von Stability AI, eine destillierte " + "Version von 3.5 Large, die hochwertige Bilder in nur wenigen " + "Entrauschungsschritten für eine deutlich schnellere Generierung " + "erzeugt. Dies ist ein zugangsbeschränktes Hugging-Face-Repository, " + "daher erfordert der Download eine vorherige Authentifizierung mit einem " + "Zugriffstoken. Die Gewichte werden in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/stabilityai/stable-" + "diffusion-3.5-large-turbo" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Large Turbo,是 3.5 Large " + "的蒸馏版本,仅需少数几个去噪步骤即可生成高质量图像,从而大幅加快生成速度" + "。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large-turbo" + ), + ) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py index 40aec1b3b..a5fe8e81c 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,60 +29,6 @@ class StableDiffusionXLSchema(BaseSchema): ``StableDiffusionXLModel``. """ - model_name: schema_field( - enum_field( - enum=[ - "stabilityai/stable-diffusion-xl-base-1.0", - "SG161222/RealVisXL_V4.0", - ] - ), - placeholder="stabilityai/stable-diffusion-xl-base-1.0", - description=MultilingualString( - en=( - "The Stable Diffusion XL checkpoint to load. " - "'stable-diffusion-xl-base-1.0' is the official base model trained " - "at 1024x1024 px for high-quality photorealistic generation. " - "'RealVisXL_V4.0' is a popular community fine-tune of SDXL " - "optimized for realistic portraits and photography." - ), - es=( - "El checkpoint Stable Diffusion XL a cargar. " - "'stable-diffusion-xl-base-1.0' es el modelo base oficial entrenado " - "a 1024x1024 px para generación fotorrealista de alta calidad. " - "'RealVisXL_V4.0' es un popular fine-tune comunitario de SDXL " - "optimizado para retratos realistas y fotografía." - ), - pt=( - "O checkpoint Stable Diffusion XL a carregar. " - "'stable-diffusion-xl-base-1.0' é o modelo base oficial treinado " - "a 1024x1024 px para geração fotorrealista de alta qualidade. " - "'RealVisXL_V4.0' é um popular fine-tune comunitário do SDXL " - "otimizado para retratos realistas e fotografia." - ), - de=( - "Der zu ladende Stable Diffusion XL-Checkpoint. " - "'stable-diffusion-xl-base-1.0' ist das offizielle Basismodell, " - "bei 1024x1024 px für hochwertige fotorealistische Generierung " - "trainiert. " - "'RealVisXL_V4.0' ist ein beliebter Community-Fine-Tune von SDXL, " - "optimiert für realistische Porträts und Fotografie." - ), - zh=( - "要加载的 Stable Diffusion XL 检查点。" - "'stable-diffusion-xl-base-1.0' 是官方基础模型," - "在 1024x1024px 下训练,用于高质量写实图像生成。" - "'RealVisXL_V4.0' 是针对写实人像和摄影优化的热门社区微调版本。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -393,7 +342,9 @@ class StableDiffusionXLSchema(BaseSchema): ) # type: ignore -class StableDiffusionXLModel(TextToImageGenerationTaskModel): +class StableDiffusionXLGenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Latent diffusion model for high-resolution 1024 px text-to-image generation. Wraps Stable Diffusion XL (SDXL) checkpoints. SDXL scales the standard @@ -415,6 +366,7 @@ class StableDiffusionXLModel(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionXLSchema + MODEL_NAME: str = "" COLOR: str = "#0d47a1" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL", @@ -483,9 +435,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "stabilityai/stable-diffusion-xl-base-1.0" - ) + self.model_name = self._pretrained_source(None) self.model = StableDiffusionXLPipeline.from_pretrained( self.model_name, @@ -534,3 +484,118 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusionXL(StableDiffusionXLGenerationModel): + """Stable Diffusion XL base 1.0 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-xl-base-1.0" + # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 35249410067 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion XL", + es="Stable Diffusion XL", + pt="Stable Diffusion XL", + de="Stable Diffusion XL", + zh="Stable Diffusion XL", + ) + DESCRIPTION = MultilingualString( + en=( + "Stable Diffusion XL base 1.0 by Stability AI, a large latent diffusion " + "model that uses two text encoders and a 1024x1024 px native resolution " + "for high fidelity results. It is well suited to detailed, " + "photorealistic and artistic prompts. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/stabilityai/s" + "table-diffusion-xl-base-1.0" + ), + es=( + "Stable Diffusion XL base 1.0 de Stability AI, un modelo de difusión " + "latente grande que usa dos codificadores de texto y una resolución " + "nativa de 1024x1024 px para resultados de alta fidelidad. Es adecuado " + "para prompts detallados, fotorrealistas y artísticos. Los pesos se " + "descargan en la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0" + ), + pt=( + "Stable Diffusion XL base 1.0 da Stability AI, um grande modelo de " + "difusão latente que usa dois codificadores de texto e resolução " + "nativa de 1024x1024 px para resultados de alta fidelidade. É adequado " + "para prompts detalhados, fotorrealistas e artísticos. Os pesos são " + "baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0" + ), + de=( + "Stable Diffusion XL Basis 1.0 von Stability AI, ein großes latentes " + "Diffusionsmodell mit zwei Textencodern und einer nativen Auflösung von " + "1024x1024 px für Ergebnisse mit hoher Detailtreue. Es eignet sich gut " + "für detaillierte, fotorealistische und künstlerische Prompts. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-xl-base" + "-1.0" + ), + zh=( + "Stability AI 推出的 Stable Diffusion XL base " + "1.0,是一种大型潜在扩散模型,使用两个文本编码器和 1024x1024 " + "像素的原生分辨率以获得高保真结果。非常适合细致、写实和艺术性的提示词。权" + "重会下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/stabi" + "lityai/stable-diffusion-xl-base-1.0" + ), + ) + + +class RealVisXLV4(StableDiffusionXLGenerationModel): + """RealVisXL V4.0 photorealistic SDXL checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "SG161222/RealVisXL_V4.0" + # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 27754923233 + DISPLAY_NAME = MultilingualString( + en="RealVisXL V4.0", + es="RealVisXL V4.0", + pt="RealVisXL V4.0", + de="RealVisXL V4.0", + zh="RealVisXL V4.0", + ) + DESCRIPTION = MultilingualString( + en=( + "RealVisXL V4.0 by SG161222, a community fine-tune of Stable Diffusion " + "XL focused on photorealism. It excels at lifelike portraits, lighting " + "and textures while remaining compatible with the SDXL pipeline. Weights " + "are downloaded into the component's own folder. Model page: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + es=( + "RealVisXL V4.0 de SG161222, un ajuste comunitario de Stable Diffusion " + "XL enfocado en el fotorrealismo. Destaca en retratos, iluminación y " + "texturas realistas, manteniéndose compatible con el pipeline de SDXL. " + "Los pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + pt=( + "RealVisXL V4.0 de SG161222, um ajuste comunitário do Stable Diffusion " + "XL focado em fotorrealismo. Destaca-se em retratos, iluminação e " + "texturas realistas, mantendo compatibilidade com o pipeline do SDXL. Os " + "pesos são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + de=( + "RealVisXL V4.0 von SG161222, eine Community-Feinabstimmung von Stable " + "Diffusion XL mit Fokus auf Fotorealismus. Es glänzt bei lebensechten " + "Porträts, Beleuchtung und Texturen und bleibt mit der SDXL-Pipeline " + "kompatibel. Die Gewichte werden in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/SG161222/RealVisXL_" + "V4.0" + ), + zh=( + "SG161222 推出的 RealVisXL V4.0,是 Stable Diffusion XL " + "的社区微调版本,专注于照片级真实感。擅长逼真的人像、光照和纹理,同时保持" + "与 SDXL 流水线的兼容。权重会下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + ) diff --git a/DashAI/back/models/hugging_face/t5_small_transformer.py b/DashAI/back/models/hugging_face/t5_small_transformer.py index 91373a064..37a1ba8f6 100644 --- a/DashAI/back/models/hugging_face/t5_small_transformer.py +++ b/DashAI/back/models/hugging_face/t5_small_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import enum_field, schema_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -73,7 +76,7 @@ class T5SmallTransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class T5SmallTransformer(TranslationModel): +class T5SmallTransformer(HFPretrainedDownloadMixin, TranslationModel): """T5-small seq2seq model for English-to-{German, French, Romanian} translation. Fine-tunes the ``t5-small`` checkpoint from Google. Translation direction is @@ -102,38 +105,40 @@ class T5SmallTransformer(TranslationModel): en=( "Google T5-small model for English-to-{German, French, Romanian} " "translation using task prefixes. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo T5-small de Google para traducción inglés-{alemán, francés, " "rumano} usando prefijos de tarea. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo T5-small do Google para tradução inglês-{alemão, francês, " "romeno} usando prefixos de tarefa. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Google T5-small-Modell für Englisch-zu-{Deutsch, Französisch, Rumänisch}-" "Übersetzung mit Aufgabenpräfixen. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "谷歌 T5-small 模型,通过任务前缀实现英语到德语/法语/罗马尼亚语翻译。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#00695C" ICON: str = "Language" + MODEL_NAME: str = "t5-small" + DOWNLOAD_SIZE_BYTES: int = 486302401 - def __init__(self, model=None, **kwargs): + def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) from transformers import AutoTokenizer - self.model_name = "t5-small" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.target_language = kwargs.get("target_language", "German") @@ -318,6 +323,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -341,6 +347,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/tongyi_z_image_model.py b/DashAI/back/models/hugging_face/tongyi_z_image_model.py index e1af35586..30ac8f3c8 100644 --- a/DashAI/back/models/hugging_face/tongyi_z_image_model.py +++ b/DashAI/back/models/hugging_face/tongyi_z_image_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,61 +29,6 @@ class TongyiZImageSchema(BaseSchema): ``TongyiZImageModel``. """ - model_name: schema_field( - enum_field(enum=["Tongyi-MAI/Z-Image", "Tongyi-MAI/Z-Image-Turbo"]), - placeholder="Tongyi-MAI/Z-Image", - description=MultilingualString( - en=( - "The Tongyi Z-Image checkpoint to load. " - "'Tongyi-Z-Image' is Alibaba's 6B parameter text-to-image model " - "using a unique S3-DiT (Sparse Spatial-Spectral Diffusion Transformer) " - "architecture, one of the most downloaded models on " - "Hugging Face. It outperforms previous open source state of the art " - "models at a fraction of their parameter count." - ), - es=( - "El checkpoint Tongyi Z-Image a cargar. " - "'Tongyi-Z-Image' es el modelo de texto a imagen de 6B parámetros de " - "Alibaba que usa una arquitectura S3-DiT única (Sparse " - "Spatial-Spectral Diffusion Transformer), uno de los " - "más descargados en " - "Hugging Face. Supera a modelos de última generación anteriores con " - "una fracción de su cantidad de parámetros." - ), - pt=( - "O checkpoint Tongyi Z-Image a carregar. " - "'Tongyi-Z-Image' é o modelo de texto para imagem de 6B parâmetros " - "da Alibaba que usa uma arquitetura S3-DiT única (Sparse " - "Spatial-Spectral Diffusion Transformer), um dos " - "mais baixados no " - "Hugging Face. Supera modelos anteriores de última geração com " - "uma fração de sua quantidade de parâmetros." - ), - de=( - "Der zu ladende Tongyi Z-Image-Checkpoint. " - "'Tongyi-Z-Image' ist Alibabas 6B-Parameter-Text-zu-Bild-Modell " - "mit einer einzigartigen S3-DiT-Architektur (Sparse Spatial-Spectral " - "Diffusion Transformer), eines der am häufigsten heruntergeladenen " - "Modelle auf Hugging Face. Es übertrifft frühere Open-Source-Modelle " - "auf dem neuesten Stand bei einem Bruchteil deren Parameteranzahl." - ), - zh=( - "要加载的 Tongyi Z-Image 检查点。" - "'Tongyi-Z-Image' 是阿里巴巴的 60 亿参数文本到图像模型," - "采用独特的 S3-DiT 架构(稀疏空间-频谱扩散变换器)," - "是 Hugging Face 上下载量最高的模型之一。" - "以更少的参数量超越了此前的开源最先进模型。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -360,7 +308,9 @@ class TongyiZImageSchema(BaseSchema): ) # type: ignore -class TongyiZImageModel(TextToImageGenerationTaskModel): +class TongyiZImageGenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Tongyi Z-Image S3-DiT model for high quality text-to-image generation. Wraps Alibaba's 6B parameter Tongyi Z-Image pipeline. The model uses a @@ -376,6 +326,7 @@ class TongyiZImageModel(TextToImageGenerationTaskModel): """ SCHEMA = TongyiZImageSchema + MODEL_NAME: str = "" COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Tongyi Z-Image", @@ -443,7 +394,7 @@ def __init__(self, **kwargs): ) self.model = DiffusionPipeline.from_pretrained( - kwargs.get("model_name"), + self._pretrained_source(None), torch_dtype=torch.float16 if use_gpu else torch.float32, ).to(self.device) @@ -485,3 +436,109 @@ def generate(self, input: str) -> List[Any]: num_images_per_prompt=self.num_images_per_prompt, ) return output.images + + +class TongyiZImage(TongyiZImageGenerationModel): + """Tongyi Z-Image text-to-image checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "Tongyi-MAI/Z-Image" + DOWNLOAD_SIZE_BYTES: int = 20547479575 + DISPLAY_NAME = MultilingualString( + en="Tongyi Z-Image", + es="Tongyi Z-Image", + pt="Tongyi Z-Image", + de="Tongyi Z-Image", + zh="Tongyi Z-Image", + ) + DESCRIPTION = MultilingualString( + en=( + "Z-Image by Alibaba's Tongyi lab, a modern text-to-image diffusion model " + "with strong prompt following and multilingual support. This is the " + "standard, full-quality checkpoint. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/Tongyi-MAI/Z-" + "Image" + ), + es=( + "Z-Image del laboratorio Tongyi de Alibaba, un modelo moderno de " + "difusión de texto a imagen con buen seguimiento de prompts y soporte " + "multilingüe. Este es el checkpoint estándar de máxima calidad. Los " + "pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/Tongyi-MAI/Z-Image" + ), + pt=( + "Z-Image do laboratório Tongyi da Alibaba, um modelo moderno de " + "difusão de texto para imagem com bom seguimento de prompts e suporte " + "multilíngue. Este é o checkpoint padrão de qualidade máxima. Os " + "pesos são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/Tongyi-MAI/Z-Image" + ), + de=( + "Z-Image aus Alibabas Tongyi-Labor, ein modernes " + "Text-zu-Bild-Diffusionsmodell mit guter Prompt-Befolgung und " + "mehrsprachiger Unterstützung. Dies ist der standardmäßige Checkpoint " + "in voller Qualität. Die Gewichte werden in den eigenen Ordner der " + "Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/Tongyi-MAI/Z-Image" + ), + zh=( + "阿里巴巴通义实验室推出的 Z-Image,是一种现代文本到图像扩散模型,具有出色" + "的提示词遵循能力和多语言支持。这是标准的全质量检查点。权重会下载到该组件" + "自己的文件夹中。 模型页面: https://huggingface.co/Tongyi-MAI/Z-Image" + ), + ) + + +class TongyiZImageTurbo(TongyiZImageGenerationModel): + """Tongyi Z-Image Turbo fast checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "Tongyi-MAI/Z-Image-Turbo" + DOWNLOAD_SIZE_BYTES: int = 32899667397 + DISPLAY_NAME = MultilingualString( + en="Tongyi Z-Image Turbo", + es="Tongyi Z-Image Turbo", + pt="Tongyi Z-Image Turbo", + de="Tongyi Z-Image Turbo", + zh="Tongyi Z-Image Turbo", + ) + DESCRIPTION = MultilingualString( + en=( + "Z-Image Turbo by Alibaba's Tongyi lab, a distilled variant of Z-Image " + "that generates images in far fewer denoising steps. It trades a little " + "quality for much faster generation. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/Tongyi-MAI/Z-" + "Image-Turbo" + ), + es=( + "Z-Image Turbo del laboratorio Tongyi de Alibaba, una variante destilada " + "de Z-Image que genera imágenes en muchos menos pasos de denoising. " + "Sacrifica algo de calidad a cambio de una generación mucho más " + "rápida. Los pesos se descargan en la carpeta propia del componente. " + "Página del modelo: https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + pt=( + "Z-Image Turbo do laboratório Tongyi da Alibaba, uma variante destilada " + "do Z-Image que gera imagens em muito menos passos de denoising. Troca " + "um pouco de qualidade por uma geração muito mais rápida. Os pesos " + "são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + de=( + "Z-Image Turbo aus Alibabas Tongyi-Labor, eine destillierte Variante von " + "Z-Image, die Bilder in weit weniger Entrauschungsschritten erzeugt. Sie " + "opfert etwas Qualität für eine deutlich schnellere Generierung. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + zh=( + "阿里巴巴通义实验室推出的 Z-Image Turbo,是 Z-Image " + "的蒸馏变体,可用更少的去噪步骤生成图像。以少量质量换取快得多的生成速度。" + "权重会下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/Ton" + "gyi-MAI/Z-Image-Turbo" + ), + ) diff --git a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py index 6e04652be..f6cb74142 100644 --- a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py +++ b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py @@ -35,31 +35,32 @@ class XlmRobertaTransformer(HuggingFaceTextClassificationTransformer): en=( "Multilingual RoBERTa for crosslingual text classification " "(100 languages). " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "RoBERTa multilingüe para clasificación de texto entre idiomas " "(100 idiomas). " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "RoBERTa multilingual para classificação de texto entre idiomas " "(100 idiomas). " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Mehrsprachiges RoBERTa für sprachübergreifende Textklassifikation " "(100 Sprachen). " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "支持跨语言文本分类的多语言 RoBERTa(100 种语言)。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#6A1B9A" ICON: str = "Language" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlm-roberta-base" + DOWNLOAD_SIZE_BYTES: int = 2245330190 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlm_roberta" diff --git a/DashAI/back/models/hugging_face/xlnet_transformer.py b/DashAI/back/models/hugging_face/xlnet_transformer.py index 892157c7e..56b05428b 100644 --- a/DashAI/back/models/hugging_face/xlnet_transformer.py +++ b/DashAI/back/models/hugging_face/xlnet_transformer.py @@ -34,28 +34,29 @@ class XlnetTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Autoregressive XLNet model for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo XLNet autorregresivo para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo XLNet autorregressivo para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Autoregressives XLNet-Modell für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "自回归 XLNet 模型,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#37474F" ICON: str = "AutoAwesome" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlnet-base-cased" + DOWNLOAD_SIZE_BYTES: int = 469226606 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlnet" diff --git a/DashAI/back/models/image_explainable_model.py b/DashAI/back/models/image_explainable_model.py new file mode 100644 index 000000000..cd2a7fd7e --- /dev/null +++ b/DashAI/back/models/image_explainable_model.py @@ -0,0 +1,43 @@ +"""Mixins declaring image explainer support on model classes. + +Models that support white-box image explainers inherit one of these mixins +instead of listing the explainers manually: the mixin carries the +``COMPATIBLE_COMPONENTS`` entries (merged with the task entries through the +registry MRO union) and forces the model to implement the inference +transform the explainers need to prepare input tensors. +""" + +from abc import ABC, abstractmethod + + +class OcclusionSaliencyCompatibleModel(ABC): + """Marks a torch image model as compatible with occlusion explainers. + + Any torch image model (convolutional or not) can support perturbation + based explainers such as ``OcclusionSaliency``. Subclasses must expose + the exact preprocessing they apply to input images. + """ + + COMPATIBLE_COMPONENTS = ["OcclusionSaliency"] + + @abstractmethod + def get_inference_transform(self): + """Return the transform applied to input images at inference time. + + Returns + ------- + Callable + A transform mapping a PIL image to the normalized tensor the + model consumes. + """ + raise NotImplementedError + + +class GradCamCompatibleModel(OcclusionSaliencyCompatibleModel, ABC): + """Marks a convolutional torch image model as compatible with Grad-CAM. + + Requires a convolutional backbone (Grad-CAM hooks the last ``Conv2d`` + layer). Implies occlusion saliency support. + """ + + COMPATIBLE_COMPONENTS = ["GradCam"] diff --git a/DashAI/back/models/lenet5_image_classifier.py b/DashAI/back/models/lenet5_image_classifier.py index abd50387a..66011ca03 100644 --- a/DashAI/back/models/lenet5_image_classifier.py +++ b/DashAI/back/models/lenet5_image_classifier.py @@ -11,6 +11,7 @@ ) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.base_model import BaseModel +from DashAI.back.models.image_explainable_model import GradCamCompatibleModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -318,7 +319,7 @@ def forward(self, x): return _LeNet5(input_channels, input_size, num_classes, dropout_rate) -class LeNet5ImageClassifier(BaseModel): +class LeNet5ImageClassifier(BaseModel, GradCamCompatibleModel): """LeNet-5 image classifier (LeCun et al., 1998). The original convolutional neural network architecture, featuring two @@ -328,6 +329,7 @@ class LeNet5ImageClassifier(BaseModel): SCHEMA = LeNet5ImageClassifierSchema COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + DISPLAY_NAME: str = MultilingualString( en="LeNet-5", es="LeNet-5", @@ -410,6 +412,25 @@ def __init__( self.idx_to_label = {} self.label_to_idx = {} + def get_inference_transform(self): + """Return the transform applied to input images at inference time. + + Returns + ------- + Callable + Resize and tensor conversion matching the training pipeline + (no normalization). + """ + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + ] + ) + def prepare_output(self, dataset, is_fit=False): """Encode string labels to integer indices matching the model's class order.""" import pyarrow as pa diff --git a/DashAI/back/models/mlp_image_classifier.py b/DashAI/back/models/mlp_image_classifier.py index 12293361d..cc2d32e0a 100644 --- a/DashAI/back/models/mlp_image_classifier.py +++ b/DashAI/back/models/mlp_image_classifier.py @@ -12,6 +12,7 @@ ) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.base_model import BaseModel +from DashAI.back.models.image_explainable_model import OcclusionSaliencyCompatibleModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -350,7 +351,7 @@ def forward(self, x): return _MLP(input_dim, output_dim, hidden_dims, dropout_rate) -class MLPImageClassifier(BaseModel): +class MLPImageClassifier(BaseModel, OcclusionSaliencyCompatibleModel): """MLP-based image classifier. A feed-forward neural network that flattens image pixels and passes them @@ -359,6 +360,7 @@ class MLPImageClassifier(BaseModel): SCHEMA = MLPImageClassifierSchema COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + DISPLAY_NAME: str = MultilingualString( en="MLP Image Classifier", es="Clasificador de Imágenes MLP", @@ -445,6 +447,25 @@ def __init__( self.idx_to_label = {} self.label_to_idx = {} + def get_inference_transform(self): + """Return the transform applied to input images at inference time. + + Returns + ------- + Callable + Resize and tensor conversion matching the training pipeline + (no normalization). + """ + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + ] + ) + def prepare_output(self, dataset, is_fit=False): """Encode string labels to integer indices matching the model's class order.""" import pyarrow as pa diff --git a/DashAI/back/models/pymc/__init__.py b/DashAI/back/models/pymc/__init__.py new file mode 100644 index 000000000..9c0fa90a1 --- /dev/null +++ b/DashAI/back/models/pymc/__init__.py @@ -0,0 +1 @@ +# flake8: noqa diff --git a/DashAI/back/models/pymc/bart_regression.py b/DashAI/back/models/pymc/bart_regression.py new file mode 100644 index 000000000..a9026be1a --- /dev/null +++ b/DashAI/back/models/pymc/bart_regression.py @@ -0,0 +1,452 @@ +"""DashAI Bayesian Additive Regression Trees (BART) regression model. + +This module wraps ``pymc-bart`` behind a small scikit-learn-style estimator +(:class:`PyMCBARTRegressor`) so it can be plugged into the same +``RegressionModel`` / ``SklearnLikeRegressor`` machinery used by the other +DashAI tabular regressors. +""" + +from typing import TYPE_CHECKING + +from sklearn.base import BaseEstimator, RegressorMixin + +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + none_type, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.models.regression_model import RegressionModel +from DashAI.back.models.scikit_learn.sklearn_like_regressor import SklearnLikeRegressor + +if TYPE_CHECKING: + import numpy as np + + +class PyMCBARTRegressor(BaseEstimator, RegressorMixin): + """A minimal scikit-learn-style wrapper around ``pymc_bart``. + + ``pymc-bart`` exposes BART as a PyMC distribution rather than as an + estimator with ``fit`` / ``predict`` methods, and its sampled trees are the + only object needed to predict on new data. This class hides that: ``fit`` + builds a PyMC model, samples the posterior with the PGBART sampler and keeps + the sampled tree ensembles; ``predict`` evaluates those trees on new inputs + and returns the posterior mean of the regression function. + + Only the sampled trees (plain Python/NumPy objects) are stored on the + fitted instance, so the estimator serialises cleanly with ``joblib`` -- the + heavy PyTensor graph is not retained. + + Parameters + ---------- + m : int + Number of trees in the ensemble. + alpha : float + Controls the prior probability over the depth of the trees. Must lie in + the open interval (0, 1). + beta : float + Controls the prior probability over the number of leaves. Must be + positive. + response : str + How leaf-node values are computed: ``constant`` (default), ``linear`` or + ``mix``. The last two are experimental in ``pymc-bart``. + draws : int + Number of posterior samples to draw per chain. + tune : int + Number of tuning (burn-in) iterations per chain, discarded afterwards. + chains : int + Number of independent MCMC chains to run. + random_seed : int, optional + Seed for the sampler and the prediction RNG, for reproducibility. + """ + + def __init__( + self, + m: int = 50, + alpha: float = 0.95, + beta: float = 2.0, + response: str = "constant", + draws: int = 200, + tune: int = 200, + chains: int = 1, + random_seed: int = 0, + ) -> None: + self.m = m + self.alpha = alpha + self.beta = beta + self.response = response + self.draws = draws + self.tune = tune + self.chains = chains + self.random_seed = random_seed + + def fit(self, x, y) -> "PyMCBARTRegressor": + """Sample the BART posterior for the regression of ``y`` on ``x``. + + Parameters + ---------- + x : array-like of shape (n_samples, n_features) + Training covariates. + y : array-like of shape (n_samples,) + Training targets. + + Returns + ------- + PyMCBARTRegressor + The fitted estimator. + """ + import numpy as np + import pymc as pm + import pymc_bart as pmb + + x = np.asarray(x, dtype="float64") + y = np.asarray(y, dtype="float64").ravel() + + sigma_prior = float(np.std(y)) + if not sigma_prior > 0: + sigma_prior = 1.0 + + with pm.Model(): + x_data = pm.Data("X", x) + mu = pmb.BART( + "mu", + x_data, + y, + m=self.m, + alpha=self.alpha, + beta=self.beta, + response=self.response, + ) + sigma = pm.HalfNormal("sigma", sigma_prior) + pm.Normal("y", mu=mu, sigma=sigma, observed=y, shape=mu.shape) + pm.sample( + draws=self.draws, + tune=self.tune, + chains=self.chains, + cores=1, + random_seed=self.random_seed, + progressbar=False, + compute_convergence_checks=False, + ) + + # Keep only the sampled tree ensembles: they are all that is needed to + # predict, and they pickle cleanly (unlike the PyTensor graph). + self.all_trees_ = list(mu.owner.op.all_trees) + self.n_features_in_ = x.shape[1] + self.y_mean_ = float(y.mean()) + return self + + def predict(self, x) -> "np.ndarray": + """Predict the posterior mean of the BART function at ``x``. + + Parameters + ---------- + x : array-like of shape (n_samples, n_features) + Covariates to predict on. + + Returns + ------- + np.ndarray of shape (n_samples,) + Posterior mean regression estimates. + """ + import numpy as np + from pymc_bart.utils import _sample_posterior + + if not hasattr(self, "all_trees_"): + raise RuntimeError( + "This PyMCBARTRegressor instance is not fitted yet. " + "Call 'fit' before using 'predict'." + ) + + x = np.asarray(x, dtype="float64") + rng = np.random.default_rng(self.random_seed) + # _sample_posterior draws `size` tree ensembles (with replacement) from + # the posterior and evaluates them on x; averaging over them yields the + # posterior mean of the regression function. + posterior = _sample_posterior( + self.all_trees_, + X=x, + rng=rng, + size=len(self.all_trees_), + shape=1, + ) + return np.asarray(posterior).mean(axis=0).squeeze(-1) + + +class BARTRegressionSchema(BaseSchema): + """Schema that configures the Bayesian Additive Regression Trees model. + + BART models the regression function as a sum of regression trees, each + constrained by a regularising prior so that individual trees are weak + learners. The posterior over the tree ensemble is sampled with MCMC, + yielding both point predictions and a full predictive distribution. The + underlying implementation is ``pymc-bart``. + """ + + m: schema_field( + int_field(ge=1), + placeholder=50, + description=MultilingualString( + en="The number of trees in the sum-of-trees ensemble.", + es="El número de árboles en el ensamble de suma de árboles.", + pt="O número de árvores no conjunto de soma de árvores.", + de="Die Anzahl der Bäume im Summe-von-Bäumen-Ensemble.", + zh="树集成中树的数量。", + ), + alias=MultilingualString( + en="Number of trees", + es="Número de árboles", + pt="Número de árvores", + de="Anzahl der Bäume", + zh="树的数量", + ), + ) # type: ignore + + alpha: schema_field( + float_field(gt=0.0, lt=1.0), + placeholder=0.95, + description=MultilingualString( + en=( + "Base of the tree-depth prior; the probability that a node at " + "depth d is non-terminal is alpha * (1 + d) ** (-beta). Must be " + "in (0, 1)." + ), + es=( + "Base del prior de profundidad del árbol; la probabilidad de que " + "un nodo a profundidad d no sea terminal es alpha * (1 + d) ** " + "(-beta). Debe estar en (0, 1)." + ), + pt=( + "Base do prior de profundidade da árvore; a probabilidade de um " + "nó na profundidade d não ser terminal é alpha * (1 + d) ** " + "(-beta). Deve estar em (0, 1)." + ), + de=( + "Basis des Baumtiefen-Priors; die Wahrscheinlichkeit, dass ein " + "Knoten in Tiefe d kein Endknoten ist, beträgt alpha * (1 + d) " + "** (-beta). Muss in (0, 1) liegen." + ), + zh="树深度先验的基数;深度为 d 的节点为非终端节点的概率为 " + "alpha * (1 + d) ** (-beta)。必须在 (0, 1) 区间内。", + ), + alias=MultilingualString( + en="Alpha (depth prior)", + es="Alfa (prior de profundidad)", + pt="Alfa (prior de profundidade)", + de="Alpha (Tiefen-Prior)", + zh="Alpha(深度先验)", + ), + ) # type: ignore + + beta: schema_field( + float_field(gt=0.0), + placeholder=2.0, + description=MultilingualString( + en=( + "Exponent of the tree-depth prior; larger values penalise deep " + "trees more strongly. Must be positive." + ), + es=( + "Exponente del prior de profundidad del árbol; valores mayores " + "penalizan más los árboles profundos. Debe ser positivo." + ), + pt=( + "Expoente do prior de profundidade da árvore; valores maiores " + "penalizam mais as árvores profundas. Deve ser positivo." + ), + de=( + "Exponent des Baumtiefen-Priors; größere Werte bestrafen tiefe " + "Bäume stärker. Muss positiv sein." + ), + zh="树深度先验的指数;较大的值对深树的惩罚更强。必须为正。", + ), + alias=MultilingualString( + en="Beta (depth prior)", + es="Beta (prior de profundidad)", + pt="Beta (prior de profundidade)", + de="Beta (Tiefen-Prior)", + zh="Beta(深度先验)", + ), + ) # type: ignore + + response: schema_field( + enum_field(enum=["constant", "linear", "mix"]), + placeholder="constant", + description=MultilingualString( + en=( + "How leaf-node values are computed. 'constant' is recommended; " + "'linear' and 'mix' are experimental." + ), + es=( + "Cómo se calculan los valores de los nodos hoja. Se recomienda " + "'constant'; 'linear' y 'mix' son experimentales." + ), + pt=( + "Como os valores dos nós folha são calculados. 'constant' é " + "recomendado; 'linear' e 'mix' são experimentais." + ), + de=( + "Wie die Werte der Blattknoten berechnet werden. 'constant' wird " + "empfohlen; 'linear' und 'mix' sind experimentell." + ), + zh="叶节点值的计算方式。推荐 'constant';'linear' 和 'mix' 为实验性。", + ), + alias=MultilingualString( + en="Leaf response", + es="Respuesta de hoja", + pt="Resposta de folha", + de="Blatt-Antwort", + zh="叶响应", + ), + ) # type: ignore + + draws: schema_field( + int_field(ge=1), + placeholder=200, + description=MultilingualString( + en="Number of posterior samples drawn per chain.", + es="Número de muestras posteriores extraídas por cadena.", + pt="Número de amostras posteriores extraídas por cadeia.", + de="Anzahl der pro Kette gezogenen Posterior-Stichproben.", + zh="每条链抽取的后验样本数量。", + ), + alias=MultilingualString( + en="Posterior draws", + es="Muestras posteriores", + pt="Amostras posteriores", + de="Posterior-Ziehungen", + zh="后验抽样数", + ), + ) # type: ignore + + tune: schema_field( + int_field(ge=0), + placeholder=200, + description=MultilingualString( + en="Number of tuning (burn-in) iterations per chain, discarded.", + es=("Número de iteraciones de ajuste (burn-in) por cadena, descartadas."), + pt=("Número de iterações de ajuste (burn-in) por cadeia, descartadas."), + de="Anzahl der Tuning-(Burn-in-)Iterationen pro Kette, verworfen.", + zh="每条链的调优(预热)迭代次数,之后被丢弃。", + ), + alias=MultilingualString( + en="Tuning iterations", + es="Iteraciones de ajuste", + pt="Iterações de ajuste", + de="Tuning-Iterationen", + zh="调优迭代次数", + ), + ) # type: ignore + + chains: schema_field( + int_field(ge=1), + placeholder=1, + description=MultilingualString( + en="Number of independent MCMC chains to run.", + es="Número de cadenas MCMC independientes a ejecutar.", + pt="Número de cadeias MCMC independentes a executar.", + de="Anzahl der auszuführenden unabhängigen MCMC-Ketten.", + zh="要运行的独立 MCMC 链的数量。", + ), + alias=MultilingualString( + en="MCMC chains", + es="Cadenas MCMC", + pt="Cadeias MCMC", + de="MCMC-Ketten", + zh="MCMC 链数", + ), + ) # type: ignore + + random_seed: schema_field( + none_type(int_field(ge=0)), + placeholder=0, + description=MultilingualString( + en="Seed for the sampler and prediction RNG, for reproducibility.", + es=( + "Semilla para el muestreador y el RNG de predicción, para " + "reproducibilidad." + ), + pt=( + "Semente para o amostrador e o RNG de predição, para reprodutibilidade." + ), + de=( + "Startwert für den Sampler und den Vorhersage-RNG, zur " + "Reproduzierbarkeit." + ), + zh="采样器和预测随机数生成器的种子,用于可复现性。", + ), + alias=MultilingualString( + en="Random seed", + es="Semilla aleatoria", + pt="Semente aleatória", + de="Zufalls-Seed", + zh="随机种子", + ), + ) # type: ignore + + +class BARTRegression(RegressionModel, SklearnLikeRegressor, PyMCBARTRegressor): + """Bayesian Additive Regression Trees regressor. + + BART represents the regression function as a sum of ``m`` regression trees. + A regularising prior keeps each tree shallow so that it acts as a weak + learner, and the posterior distribution over the whole ensemble is explored + with an MCMC sampler (Particle Gibbs for the trees). Predictions are the + posterior mean of the sum-of-trees function, and the sampled posterior also + provides a natural quantification of predictive uncertainty. + + Key hyperparameters are the number of trees ``m`` and the tree-structure + prior parameters ``alpha`` and ``beta``, together with the MCMC controls + ``draws``, ``tune`` and ``chains``. The implementation wraps ``pymc-bart``. + + References + ---------- + - [1] Chipman, H.A., George, E.I. & McCulloch, R.E. (2010). "BART: Bayesian + Additive Regression Trees." The Annals of Applied Statistics, 4(1), + 266-298. https://doi.org/10.1214/09-AOAS285 + - [2] https://www.pymc.io/projects/bart/ + """ + + SCHEMA = BARTRegressionSchema + DISPLAY_NAME: str = MultilingualString( + en="BART Regression", + es="Regresión BART", + pt="Regressão BART", + de="BART-Regression", + zh="BART 回归", + ) + DESCRIPTION: str = MultilingualString( + en=( + "Bayesian sum-of-trees regression that samples the posterior over a " + "tree ensemble with MCMC." + ), + es=( + "Regresión bayesiana de suma de árboles que muestrea la posterior " + "sobre un ensamble de árboles con MCMC." + ), + pt=( + "Regressão bayesiana de soma de árvores que amostra a posterior " + "sobre um conjunto de árvores com MCMC." + ), + de=( + "Bayessche Summe-von-Bäumen-Regression, die die Posterior über ein " + "Baum-Ensemble mit MCMC abtastet." + ), + zh="贝叶斯树求和回归,使用 MCMC 对树集成的后验进行采样。", + ) + COLOR: str = "#26A69A" + ICON: str = "Park" + + def __init__(self, **kwargs) -> None: + """Initialise the model by forwarding all kwargs to the parent class. + + Parameters + ---------- + **kwargs : dict + Hyperparameter values forwarded to the parent wrapper. See the + associated schema class for available keys and their defaults. + """ + super().__init__(**kwargs) diff --git a/DashAI/back/models/resnet18_image_classifier.py b/DashAI/back/models/resnet18_image_classifier.py index aed89a626..bcc8fee43 100644 --- a/DashAI/back/models/resnet18_image_classifier.py +++ b/DashAI/back/models/resnet18_image_classifier.py @@ -1,13 +1,14 @@ """ResNet-18 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class ResNet18ImageClassifier(TorchvisionImageClassifier): +class ResNet18ImageClassifier(TorchvisionDownloadMixin, TorchvisionImageClassifier): """ResNet-18 image classifier (He et al., 2015). 18-layer residual network with skip connections that solve the vanishing @@ -52,13 +53,21 @@ class ResNet18ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#2E7D32" ICON: str = "AccountTree" + DOWNLOAD_SIZE_BYTES: int = 47_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import ResNet18_Weights + + return ResNet18_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import ResNet18_Weights, resnet18 weights = ResNet18_Weights.DEFAULT if pretrained else None - model = resnet18(weights=weights) + with self.local_hub(): + model = resnet18(weights=weights) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/DashAI/back/models/resnet50_image_classifier.py b/DashAI/back/models/resnet50_image_classifier.py index 92f07a662..c556a3960 100644 --- a/DashAI/back/models/resnet50_image_classifier.py +++ b/DashAI/back/models/resnet50_image_classifier.py @@ -1,13 +1,14 @@ """ResNet-50 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class ResNet50ImageClassifier(TorchvisionImageClassifier): +class ResNet50ImageClassifier(TorchvisionDownloadMixin, TorchvisionImageClassifier): """ResNet-50 image classifier (He et al., 2015). 50-layer residual network using bottleneck blocks. Deeper and more @@ -54,13 +55,21 @@ class ResNet50ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#1B5E20" ICON: str = "AccountTree" + DOWNLOAD_SIZE_BYTES: int = 100_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import ResNet50_Weights + + return ResNet50_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import ResNet50_Weights, resnet50 weights = ResNet50_Weights.DEFAULT if pretrained else None - model = resnet50(weights=weights) + with self.local_hub(): + model = resnet50(weights=weights) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/DashAI/back/models/scikit_learn/decision_tree_classifier.py b/DashAI/back/models/scikit_learn/decision_tree_classifier.py index cfeab27e0..fcabdebdb 100644 --- a/DashAI/back/models/scikit_learn/decision_tree_classifier.py +++ b/DashAI/back/models/scikit_learn/decision_tree_classifier.py @@ -184,6 +184,46 @@ class DecisionTreeClassifierSchema(BaseSchema): zh="最大特征数", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class DecisionTreeClassifier( diff --git a/DashAI/back/models/scikit_learn/extra_trees_classifier.py b/DashAI/back/models/scikit_learn/extra_trees_classifier.py index cb2f5ba4c..4f0efbe8b 100644 --- a/DashAI/back/models/scikit_learn/extra_trees_classifier.py +++ b/DashAI/back/models/scikit_learn/extra_trees_classifier.py @@ -3,6 +3,7 @@ from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, + enum_field, none_type, optimizer_int_field, schema_field, @@ -198,6 +199,54 @@ class ExtraTreesClassifierSchema(BaseSchema): zh="随机状态", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced", "balanced_subsample"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' adjusts weights inversely proportional to " + "class frequencies in the whole dataset; 'balanced_subsample' does " + "the same but per bootstrap sample of each tree. Use None for no " + "weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta los pesos de forma inversamente " + "proporcional a la frecuencia de cada clase en todo el conjunto de " + "datos; 'balanced_subsample' hace lo mismo pero por cada muestra " + "bootstrap de cada árbol. Use None para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta os pesos de forma " + "inversamente proporcional à frequência de cada classe em todo o " + "conjunto de dados; 'balanced_subsample' faz o mesmo, mas por " + "amostra bootstrap de cada árvore. Use None para não aplicar " + "ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte umgekehrt proportional zur Klassenhäufigkeit im " + "gesamten Datensatz an; 'balanced_subsample' tut dasselbe, jedoch " + "pro Bootstrap-Stichprobe jedes Baums. Verwenden Sie None für " + "keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'根据整个数据集中" + "各类别频率的反比调整权重;'balanced_subsample'则对每棵树的自举" + "采样分别执行相同操作。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class ExtraTreesClassifier( diff --git a/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py b/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py index 515914e7c..21af0271d 100644 --- a/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py +++ b/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py @@ -4,6 +4,8 @@ from DashAI.back.core.schema_fields import ( BaseSchema, + enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -228,6 +230,46 @@ class HistGradientBoostingClassifierSchema(BaseSchema): zh="L2正则化", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class HistGradientBoostingClassifier( diff --git a/DashAI/back/models/scikit_learn/linear_svc_classifier.py b/DashAI/back/models/scikit_learn/linear_svc_classifier.py index dcb314d91..dcc7cbff6 100644 --- a/DashAI/back/models/scikit_learn/linear_svc_classifier.py +++ b/DashAI/back/models/scikit_learn/linear_svc_classifier.py @@ -192,6 +192,47 @@ class LinearSVCClassifierSchema(BaseSchema): ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore + class LinearSVCClassifier( TabularClassificationModel, SklearnLikeClassifier, _LinearSVC @@ -281,7 +322,15 @@ def train(self, x_train, y_train, x_validation=None, y_validation=None): params = { k: getattr(self, k) - for k in ["C", "loss", "max_iter", "tol", "fit_intercept", "random_state"] + for k in [ + "C", + "loss", + "max_iter", + "tol", + "fit_intercept", + "random_state", + "class_weight", + ] if hasattr(self, k) } base = _LinearSVCRaw(**params) diff --git a/DashAI/back/models/scikit_learn/logistic_regression.py b/DashAI/back/models/scikit_learn/logistic_regression.py index 9aa5f4dc8..2efc4581e 100644 --- a/DashAI/back/models/scikit_learn/logistic_regression.py +++ b/DashAI/back/models/scikit_learn/logistic_regression.py @@ -3,6 +3,7 @@ from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -114,6 +115,46 @@ class LogisticRegressionSchema(BaseSchema): zh="最大迭代次数", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class LogisticRegression( diff --git a/DashAI/back/models/scikit_learn/random_forest_classifier.py b/DashAI/back/models/scikit_learn/random_forest_classifier.py index 7c6104b1a..1b808b886 100644 --- a/DashAI/back/models/scikit_learn/random_forest_classifier.py +++ b/DashAI/back/models/scikit_learn/random_forest_classifier.py @@ -1,6 +1,12 @@ from sklearn.ensemble import RandomForestClassifier as _RandomForestClassifier -from DashAI.back.core.schema_fields import BaseSchema, optimizer_int_field, schema_field +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + none_type, + optimizer_int_field, + schema_field, +) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.scikit_learn.sklearn_like_classifier import ( SklearnLikeClassifier, @@ -224,6 +230,54 @@ class RandomForestClassifierSchema(BaseSchema): zh="随机状态", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced", "balanced_subsample"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' adjusts weights inversely proportional to " + "class frequencies in the whole dataset; 'balanced_subsample' does " + "the same but per bootstrap sample of each tree. Use None for no " + "weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta los pesos de forma inversamente " + "proporcional a la frecuencia de cada clase en todo el conjunto de " + "datos; 'balanced_subsample' hace lo mismo pero por cada muestra " + "bootstrap de cada árbol. Use None para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta os pesos de forma " + "inversamente proporcional à frequência de cada classe em todo o " + "conjunto de dados; 'balanced_subsample' faz o mesmo, mas por " + "amostra bootstrap de cada árvore. Use None para não aplicar " + "ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte umgekehrt proportional zur Klassenhäufigkeit im " + "gesamten Datensatz an; 'balanced_subsample' tut dasselbe, jedoch " + "pro Bootstrap-Stichprobe jedes Baums. Verwenden Sie None für " + "keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'根据整个数据集中" + "各类别频率的反比调整权重;'balanced_subsample'则对每棵树的自举" + "采样分别执行相同操作。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class RandomForestClassifier( diff --git a/DashAI/back/models/scikit_learn/sgd_classifier.py b/DashAI/back/models/scikit_learn/sgd_classifier.py index 335471f54..f1a445d46 100644 --- a/DashAI/back/models/scikit_learn/sgd_classifier.py +++ b/DashAI/back/models/scikit_learn/sgd_classifier.py @@ -234,6 +234,47 @@ class SGDClassifierSchema(BaseSchema): ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore + class SGDClassifier(TabularClassificationModel, SklearnLikeClassifier, _SGDClassifier): """SGD classifier with probability calibration for consistent predict_proba output. @@ -318,6 +359,7 @@ def train(self, x_train, y_train, x_validation=None, y_validation=None): "tol", "learning_rate", "random_state", + "class_weight", ] if hasattr(self, k) } diff --git a/DashAI/back/models/scikit_learn/sklearn_like_classifier.py b/DashAI/back/models/scikit_learn/sklearn_like_classifier.py index 919f81a57..d6eda8167 100644 --- a/DashAI/back/models/scikit_learn/sklearn_like_classifier.py +++ b/DashAI/back/models/scikit_learn/sklearn_like_classifier.py @@ -9,15 +9,21 @@ class SklearnLikeClassifier(SklearnLikeModel): - """Abstract mixin for scikit-learn-style classification models. + """Abstract mixin for scikit-learn style classification models. Extends ``SklearnLikeModel`` with a ``predict`` method that converts a ``DashAIDataset`` into a NumPy array, calls the wrapped sklearn estimator's - ``predict_proba``, and returns the class-probability matrix. Concrete + ``predict_proba``, and returns the class probability matrix. Concrete classifier wrappers (e.g. ``SVC``, ``RandomForestClassifier``) inherit from this class and from a ``BaseSchema`` subclass. + + Declares the model specific explainers that need sklearn classifier + semantics (``predict_proba``); subclasses inherit them through the + registry's MRO merge of ``COMPATIBLE_COMPONENTS``. """ + COMPATIBLE_COMPONENTS = ["DiceCounterfactual"] + def predict(self, x_pred: "DashAIDataset") -> "ndarray": """Make a prediction with the model diff --git a/DashAI/back/models/scikit_learn/svc.py b/DashAI/back/models/scikit_learn/svc.py index 2257c3398..bba3ecf0b 100644 --- a/DashAI/back/models/scikit_learn/svc.py +++ b/DashAI/back/models/scikit_learn/svc.py @@ -4,6 +4,7 @@ BaseSchema, bool_field, enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -222,6 +223,46 @@ class SVCSchema(BaseSchema): en="tolerance", es="tolerancia", pt="tolerância", de="Toleranz", zh="容差" ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class SVC(TabularClassificationModel, SklearnLikeClassifier, _SVC): @@ -254,52 +295,11 @@ class SVC(TabularClassificationModel, SklearnLikeClassifier, _SVC): de="Support-Vektor-Maschine (SVM)", ) DESCRIPTION: str = MultilingualString( - en=( - "Support Vector Machine (SVM) is a supervised machine learning algorithm " - "used for classification and regression tasks. It works by finding the " - "optimal hyperplane that maximizes the margin between different classes " - "in a high dimensional feature space. SVMs are effective in cases where " - "the number of features is large relative to the number of samples and " - "can model complex, nonlinear decision boundaries through the use of " - "kernel functions such as linear, polynomial, and radial basis function " - "(RBF) kernels." - ), - es=( - "La Máquina de Vectores de Soporte (SVM) es un algoritmo de aprendizaje " - "automático supervisado utilizado para tareas de clasificación y " - "regresión. Funciona encontrando el hiperplano óptimo que maximiza el " - "margen entre las distintas clases en un espacio de características de " - "alta dimensionalidad. Las SVM son especialmente efectivas cuando el " - "número de características es grande en relación con el número de " - "muestras y pueden modelar fronteras de decisión complejas y no lineales " - "mediante el uso de funciones kernel como lineal, polinomial y de base " - "radial (RBF)." - ), - pt=( - "A Máquina de Vetores de Suporte (SVM) é um algoritmo de aprendizado " - "de máquina supervisionado utilizado para tarefas de classificação e " - "regressão. Funciona encontrando o hiperplano ótimo que maximiza a " - "margem entre as diferentes classes em um espaço de características de " - "alta dimensionalidade. As SVMs são especialmente eficazes quando o " - "número de características é grande em relação ao número de amostras e " - "podem modelar fronteiras de decisão complexas e não lineares mediante " - "o uso de funções kernel como linear, polinomial e de base radial (RBF)." - ), - zh=( - "支持向量机(SVM)是一种监督学习算法,通过在高维特征空间中" - "寻找最优超平面来最大化类间间隔,支持线性、多项式和径向基函数(RBF)核。" - ), - de=( - "Die Support-Vektor-Maschine (SVM) ist ein überwachter " - "Machine Learning Algorithmus für Klassifikations- und " - "Regressionsaufgaben. Sie findet die optimale Hyperebene, die die " - "Margin zwischen verschiedenen Klassen in einem hochdimensionalen " - "Merkmalsraum maximiert. SVMs sind besonders effektiv, wenn die Anzahl " - "der Merkmale im Verhältnis zur Anzahl der Stichproben groß ist, und " - "können komplexe, nichtlineare Entscheidungsgrenzen durch den Einsatz " - "von Kernelfunktionen wie linear, polynomial und radialer Basisfunktion " - "(RBF) modellieren." - ), + en="Finds the optimal hyperplane that maximises the margin between classes.", + es="Encuentra el hiperplano óptimo que maximiza el margen entre clases.", + pt="Encontra o hiperplano ótimo que maximiza a margem entre classes.", + zh="寻找最优超平面以最大化类间间隔的分类算法。", + de="Findet die optimale Hyperebene, die den Margin zwischen Klassen maximiert.", ) COLOR: str = "#FF80AB" ICON: str = "Timeline" diff --git a/DashAI/back/models/scikit_learn/tfidf_logreg_text_classification_model.py b/DashAI/back/models/scikit_learn/tfidf_logreg_text_classification_model.py index 5f1c9a354..bc2fd84f6 100644 --- a/DashAI/back/models/scikit_learn/tfidf_logreg_text_classification_model.py +++ b/DashAI/back/models/scikit_learn/tfidf_logreg_text_classification_model.py @@ -242,16 +242,46 @@ def predict(self, x): return self.classifier.predict_proba(X_tfidf) def prepare_output(self, dataset: "DashAIDataset", is_fit: bool = False): - from datasets import Dataset as HFDataset + """Label encode the target column, keeping it typed. - from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + Parameters + ---------- + dataset : DashAIDataset + The output dataset containing the target labels. + is_fit : bool, optional + If ``True``, fit the label encoder before transforming. If + ``False``, apply the existing encoding. Default is ``False``. + + Returns + ------- + DashAIDataset + The dataset with the target column encoded as integers and typed + as ``Categorical``, its categories in label encoder order (the + class index order of the model predictions). + """ + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + from DashAI.back.types.categorical import Categorical col = dataset.column_names[0] if is_fit: encoded = self.label_encoder.fit_transform(dataset[col]).tolist() else: encoded = self.label_encoder.transform(dataset[col]).tolist() - return to_dashai_dataset(HFDataset.from_dict({col: encoded})) + + # Rebuilding the dataset from the encoded values alone would drop the + # column type, and explainers read the class names from it. + types = dict(dataset.types) + types[col] = Categorical( + values=[str(label) for label in self.label_encoder.classes_], + converted=True, + ) + return modify_table( + dataset, + columns={col: pa.array(encoded, type=pa.int64())}, + types=types, + ) def save(self, filename: Union[str, "Path"]) -> None: import joblib diff --git a/DashAI/back/optimizers/base_optimizer.py b/DashAI/back/optimizers/base_optimizer.py index 15582d391..9d9750d6a 100644 --- a/DashAI/back/optimizers/base_optimizer.py +++ b/DashAI/back/optimizers/base_optimizer.py @@ -5,6 +5,7 @@ from typing import Final from DashAI.back.config_object import ConfigObject +from DashAI.back.core.artifacts import PlotlyArtifact log = logging.getLogger(__name__) @@ -87,11 +88,10 @@ def history_objective_plot(self, trials, goal_metric): Returns ------- - fig (json): json with the plot data + artifact (PlotlyArtifact): typed artifact wrapping the plot data """ # Lazy imports import numpy as np - import plotly import plotly.graph_objects as go x = list(range(1, len(trials) + 1)) @@ -126,16 +126,17 @@ def history_objective_plot(self, trials, goal_metric): line_width=2, ) ) + title = ( + "Optimization History with Current Max Value" + if goal_metric["metadata"]["maximize"] + else "Optimization History with Current Min Value" + ) fig.update_layout( - title=( - "Optimization History with Current Max Value" - if goal_metric["metadata"]["maximize"] - else "Optimization History with Current Min Value" - ), + title=title, xaxis_title="Trial", yaxis_title=goal_metric["name"], ) - return plotly.io.to_json(fig) + return PlotlyArtifact(payload=fig, title=title) def slice_plot(self, trials, goal_metric): """ @@ -148,10 +149,9 @@ def slice_plot(self, trials, goal_metric): Returns ------- - fig (json): json with the plot data + artifact (PlotlyArtifact): typed artifact wrapping the plot data """ # Lazy imports - import plotly import plotly.graph_objects as go param_names = list(trials[0]["params"].keys()) @@ -199,15 +199,16 @@ def slice_plot(self, trials, goal_metric): updatemenus = [{"buttons": buttons, "direction": "down", "showactive": True}] + title = f"Slice plot for {param_names[0]}" fig = go.Figure(data=traces) fig.update_layout( updatemenus=updatemenus, - title=f"Slice plot for {param_names[0]}", + title=title, xaxis_title=param_names[0], yaxis_title=goal_metric["name"], ) - return plotly.io.to_json(fig) + return PlotlyArtifact(payload=fig, title=title) def contour_plot(self, trials, goal_metric): """ @@ -220,10 +221,9 @@ def contour_plot(self, trials, goal_metric): Returns ------- - fig (json): json with the plot data + artifact (PlotlyArtifact): typed artifact wrapping the plot data """ # Lazy imports - import plotly import plotly.graph_objects as go param_names = list(trials[0]["params"].keys()) @@ -298,14 +298,15 @@ def contour_plot(self, trials, goal_metric): updatemenus = [{"buttons": buttons, "direction": "down", "showactive": True}] + title = f"Contour plot for {traces[0]['name']}" fig = go.Figure(data=traces + scatter_traces) fig.update_layout( updatemenus=updatemenus, - title=f"Contour plot for {traces[0]['name']}", + title=title, xaxis_title=param_names[0], yaxis_title=param_names[1], ) - return plotly.io.to_json(fig) + return PlotlyArtifact(payload=fig, title=title) def importance_plot(self, trials, goal_metric): """ @@ -318,11 +319,10 @@ def importance_plot(self, trials, goal_metric): Returns ------- - fig (json): json with the plot data + artifact (PlotlyArtifact): typed artifact wrapping the plot data """ # Lazy imports import optuna - import plotly import plotly.graph_objects as go from optuna.importance import FanovaImportanceEvaluator @@ -371,14 +371,15 @@ def importance_plot(self, trials, goal_metric): ] ) + title = "Hyperparameter importance" fig.update_layout( - title="Hyperparameter importance", + title=title, xaxis_title="Importance", yaxis_title="Hyperparameter", yaxis={"tickangle": 0}, ) - return plotly.io.to_json(fig) + return PlotlyArtifact(payload=fig, title=title) def create_plots(self, trials, run_id, n_params, goal_metric): """ @@ -395,7 +396,8 @@ def create_plots(self, trials, run_id, n_params, goal_metric): Returns ------- - fig (json): json with the plot data + plots_filenames (list): Filenames to persist each plot under. + plots_list (list): The matching list of PlotlyArtifact instances. """ if n_params >= 2: plots_filenames = [ diff --git a/DashAI/back/services/scoring_service.py b/DashAI/back/services/scoring_service.py deleted file mode 100644 index ce70e16df..000000000 --- a/DashAI/back/services/scoring_service.py +++ /dev/null @@ -1,336 +0,0 @@ -"""Scoring service for model comparison using weighted metric profiles.""" - -import logging -from typing import TYPE_CHECKING, Any, Dict, List, Optional - -from kink import inject - -if TYPE_CHECKING: - from DashAI.back.dependencies.registry import ComponentRegistry - -log = logging.getLogger(__name__) - - -class ScoringService: - """Service for calculating weighted model comparison scores. - - Profiles are defined at the Task level (BaseTask.SCORING_PROFILES). - This service fetches them from the registry and applies normalization - across different metric ranges, producing 0–100 scores per model. - - Component registry is injected when needed via @inject decorator. - """ - - @inject - def _get_metric_metadata( - self, - component_registry: "ComponentRegistry" = lambda di: di["component_registry"], - ) -> Dict[str, Dict[str, Any]]: - """Get metric metadata (maximize, normalize_ref) from registry. - - Parameters - ---------- - component_registry : ComponentRegistry - Registry to look up metrics (injected). - - Returns - ------- - Dict[str, Dict[str, Any]] - Map of metric name (str) to metadata dict. - """ - metadata = {} - metrics = component_registry.get_components_by_types(select=["Metric"]) - for metric_dict in metrics: - name = metric_dict["name"] - metadata[name] = { - "maximize": metric_dict.get("metadata", {}).get("maximize"), - "normalize_ref": metric_dict.get("metadata", {}).get("normalize_ref"), - } - return metadata - - @inject - def _get_all_profiles( - self, - component_registry: "ComponentRegistry" = lambda di: di["component_registry"], - ) -> Dict[str, Dict[str, Any]]: - """Get all profiles from all tasks in the registry. - - Parameters - ---------- - component_registry : ComponentRegistry - Registry to look up tasks and their SCORING_PROFILES (injected). - - Returns - ------- - Dict[str, Dict[str, Any]] - Map of profile_id → {description, weights, task_name}. - """ - all_profiles = {} - tasks = component_registry.get_components_by_types(select=["Task"]) - - for task_dict in tasks: - task_name = task_dict["name"] - try: - # Get task class from registry to access SCORING_PROFILES - full_task_dict = component_registry[task_name] - task_class = full_task_dict.get("class") - - if task_class and hasattr(task_class, "SCORING_PROFILES"): - profiles = task_class.SCORING_PROFILES - for profile_id, profile_data in profiles.items(): - all_profiles[profile_id] = { - "description": profile_data["description"], - "weights": profile_data["weights"], - "task_name": task_name, - } - except KeyError: - log.debug(f"Task {task_name} not found in registry") - except Exception as e: - log.warning(f"Error accessing SCORING_PROFILES for {task_name}: {e}") - - return all_profiles - - @inject - def get_available_profiles( - self, - task_name: Optional[str], - component_registry: "ComponentRegistry" = lambda di: di["component_registry"], - ) -> List[Dict[str, Any]]: - """Get profiles available for a given task. - - Fetches profiles from the task's SCORING_PROFILES class attribute - in the registry. - - Parameters - ---------- - task_name : Optional[str] - Task class name (e.g., "TabularClassificationTask") or None. - If None, returns all profiles from all tasks. - component_registry : ComponentRegistry - Registry to look up task class (injected). - - Returns - ------- - List[Dict[str, Any]] - List of profile dicts with keys: id, description, weights. - """ - if task_name: - # Access the task class directly to avoid profile-ID collisions - # that occur when multiple tasks share the same profile ID keys - # (e.g. TabularClassificationTask and ImageClassificationTask both - # define "balanced"). - try: - full_task_dict = component_registry[task_name] - task_class = full_task_dict.get("class") - if task_class and hasattr(task_class, "SCORING_PROFILES"): - return [ - { - "id": profile_id, - "description": profile_data["description"], - "weights": profile_data["weights"], - } - for profile_id, profile_data in ( - task_class.SCORING_PROFILES.items() - ) - ] - except KeyError: - log.debug(f"Task {task_name} not found in registry") - except Exception as e: - log.warning(f"Error accessing SCORING_PROFILES for {task_name}: {e}") - return [] - - # No task specified, so return one entry per unique profile ID across all tasks. - all_profiles = self._get_all_profiles() - return [ - { - "id": profile_id, - "description": profile_data["description"], - "weights": profile_data["weights"], - } - for profile_id, profile_data in all_profiles.items() - ] - - def normalize_metric_value( - self, - value: float, - maximize: Optional[bool], - normalize_ref: Optional[float], - all_values: Optional[List[float]] = None, - ) -> float: - """Normalize a metric value to [0, 1] where 1 = best. - - Three strategies: - 1. MAXIMIZE=True → clamp(value, 0, 1) - Handles metrics like CohenKappa/R² that can go negative. - 2. MAXIMIZE=False, fixed normalize_ref (e.g. HammingDistance) - → 1 − min(value / ref, 1) - 3. MAXIMIZE=False, no fixed ref (MAE, RMSE, LogLoss, TER, …) - → data-driven: (maxVal − value) / (maxVal − minVal) - - Parameters - ---------- - value : float - The metric value to normalize. - maximize : Optional[bool] - Whether higher values are better. None treated as True. - normalize_ref : Optional[float] - Fixed reference value for minimize metrics. None → data-driven. - all_values : Optional[List[float]] - All values of this metric across the comparison set. - Used for data-driven normalization. - - Returns - ------- - float - Normalized value in [0, 1]. - """ - if maximize is not False: - # Maximize: clamp to [0, 1] - return max(0.0, min(1.0, value)) - - if normalize_ref is not None: - # Fixed reference: 1 − min(value / ref, 1) - return max(0.0, 1.0 - min(value / normalize_ref, 1.0)) - - # Data-driven range normalization for unbounded minimize metrics - if all_values and len(all_values) > 0: - if len(all_values) == 1: - return 1.0 # Single value = best by definition - max_val = max(all_values) - min_val = min(all_values) - range_val = max_val - min_val - if range_val < 1e-9: - return 1.0 # All tied = all best - return max(0.0, min(1.0, (max_val - value) / range_val)) - - # Fallback when no range available: normalize against log(10) - # This should rarely occur; ideally all_values is always provided for - # data-driven metrics. Used only as a safety fallback. - return max(0.0, 1.0 - min(value / 2.303, 1.0)) - - def compute_score( - self, - metrics_dict: Dict[str, float], - profile_id: str, - metric_ranges: Optional[Dict[str, List[float]]] = None, - ) -> Optional[Dict[str, Any]]: - """Compute the weighted score [0–100] for a model given its metrics. - - Only metrics present in both the profile and metrics_dict are used. - Their weights are re-normalized so the score is meaningful even when - some metrics haven't been computed yet. - - Parameters - ---------- - metrics_dict : Dict[str, float] - Metrics for this model, keyed by metric name (e.g., {"Accuracy": 0.95}). - profile_id : str - ID of the profile to use. - metric_ranges : Optional[Dict[str, List[float]]] - For data-driven normalization: {metric_name: [all_values]}. - - Returns - ------- - Optional[Dict[str, Any]] - Dict with: - - score: float [0, 100] - - breakdown: List[{metric_name, value, normalized_weight}] - Or None if no profile metrics are available in this model. - """ - all_profiles = self._get_all_profiles() - if profile_id not in all_profiles: - return None - - profile_data = all_profiles[profile_id] - profile_weights = profile_data["weights"] - metric_metadata = self._get_metric_metadata() - - # Find metrics that are both in the profile and in the model data - available_entries = [ - (metric_name, weight) - for metric_name, weight in profile_weights.items() - if metric_name in metrics_dict and metrics_dict[metric_name] is not None - ] - - if not available_entries: - return None - - # Re-normalize weights to sum to 1 - total_weight = sum(w for _, w in available_entries) - score = 0.0 - breakdown = [] - - for metric_name, weight in available_entries: - normalized_weight = weight / total_weight - value = float(metrics_dict[metric_name]) - meta = metric_metadata.get(metric_name, {}) - maximize = meta.get("maximize") - normalize_ref = meta.get("normalize_ref") - all_values = metric_ranges.get(metric_name) if metric_ranges else None - - normalized = self.normalize_metric_value( - value, maximize, normalize_ref, all_values - ) - score += normalized_weight * normalized - breakdown.append( - { - "metric_name": metric_name, - "value": value, - "normalized_weight": normalized_weight, - "normalized_value": normalized, - } - ) - - return {"score": score * 100, "breakdown": breakdown} - - def compute_scores_for_comparison( - self, - runs_metrics: List[Dict[str, Any]], - profile_id: str, - ) -> Dict[int, Optional[Dict[str, Any]]]: - """Compute scores for all runs in a comparison. - - Collects metric ranges for data-driven normalization, then computes - per-run scores. - - Parameters - ---------- - runs_metrics : List[Dict[str, Any]] - List of dicts, each with: - - run_id: int - - metrics: Dict[str, float] (metric name → value) - profile_id : str - ID of the profile to use. - - Returns - ------- - Dict[int, Optional[Dict[str, Any]]] - Map of run_id → score dict (or None if no profile metrics). - """ - all_profiles = self._get_all_profiles() - if profile_id not in all_profiles: - return {} - - profile_data = all_profiles[profile_id] - profile_weights = profile_data["weights"] - - # Collect all values for each metric in the profile - metric_ranges: Dict[str, List[float]] = { - metric_name: [] for metric_name in profile_weights - } - - for run_data in runs_metrics: - metrics = run_data.get("metrics", {}) - for metric_name in profile_weights: - if metric_name in metrics and metrics[metric_name] is not None: - metric_ranges[metric_name].append(float(metrics[metric_name])) - - # Compute scores - result = {} - for run_data in runs_metrics: - run_id = run_data["run_id"] - metrics = run_data.get("metrics", {}) - score_data = self.compute_score(metrics, profile_id, metric_ranges) - result[run_id] = score_data - - return result diff --git a/DashAI/back/static/images/column_arithmetic.png b/DashAI/back/static/images/column_arithmetic.png new file mode 100644 index 000000000..1e22eb4d0 Binary files /dev/null and b/DashAI/back/static/images/column_arithmetic.png differ diff --git a/DashAI/back/static/images/column_concat.png b/DashAI/back/static/images/column_concat.png new file mode 100644 index 000000000..0b2176701 Binary files /dev/null and b/DashAI/back/static/images/column_concat.png differ diff --git a/DashAI/back/tasks/base_task.py b/DashAI/back/tasks/base_task.py index 94d1741f6..9dd2adaf6 100644 --- a/DashAI/back/tasks/base_task.py +++ b/DashAI/back/tasks/base_task.py @@ -15,7 +15,6 @@ class BaseTask: """Base class for DashAI compatible tasks.""" TYPE: Final[str] = "Task" - SCORING_PROFILES: Dict[str, Dict[str, Any]] = {} @property @abstractmethod diff --git a/DashAI/back/tasks/classification_task.py b/DashAI/back/tasks/classification_task.py index 5dc347d5a..b12c3a17f 100644 --- a/DashAI/back/tasks/classification_task.py +++ b/DashAI/back/tasks/classification_task.py @@ -23,6 +23,7 @@ class ClassificationTask(BaseTask): COMPATIBLE_COMPONENTS = [ "Accuracy", + "BalancedAccuracy", "Precision", "Recall", "F1", diff --git a/DashAI/back/tasks/image_classification_task.py b/DashAI/back/tasks/image_classification_task.py index acf3dd188..a38f20639 100644 --- a/DashAI/back/tasks/image_classification_task.py +++ b/DashAI/back/tasks/image_classification_task.py @@ -49,24 +49,6 @@ class ImageClassificationTask(ClassificationTask): de="Bildklassifikation", zh="图像分类", ) - SCORING_PROFILES = { - "balanced": { - "description": "Balanced", - "weights": {"Accuracy": 0.3, "F1": 0.4, "ROCAUC": 0.3}, - }, - "detectPositives": { - "description": "Detect Positives", - "weights": {"Recall": 0.6, "F1": 0.3, "Precision": 0.1}, - }, - "avoidFalseAlarms": { - "description": "Avoid False Alarms", - "weights": {"Precision": 0.6, "F1": 0.3, "Recall": 0.1}, - }, - "probabilityQuality": { - "description": "Probability Quality", - "weights": {"ROCAUC": 0.5, "LogLoss": 0.5}, - }, - } metadata: dict = { "inputs_types": [DashAIImage], "outputs_types": [Categorical], diff --git a/DashAI/back/tasks/regression_task.py b/DashAI/back/tasks/regression_task.py index 8e9abb666..9eefedae0 100644 --- a/DashAI/back/tasks/regression_task.py +++ b/DashAI/back/tasks/regression_task.py @@ -33,17 +33,6 @@ class RegressionTask(BaseTask): en="Regression", es="Regresión", pt="Regressão", de="Regression", zh="回归" ) - SCORING_PROFILES = { - "regression_fit": { - "description": "Model Fit", - "weights": {"R2": 0.6, "ExplainedVariance": 0.4}, - }, - "regression_error": { - "description": "Error Balanced", - "weights": {"R2": 0.4, "RMSE": 0.35, "MAE": 0.25}, - }, - } - metadata: dict = { "inputs_types": [Float, Integer, Categorical], "outputs_types": [Float, Integer], diff --git a/DashAI/back/tasks/tabular_classification_task.py b/DashAI/back/tasks/tabular_classification_task.py index a69c05d49..6839db912 100644 --- a/DashAI/back/tasks/tabular_classification_task.py +++ b/DashAI/back/tasks/tabular_classification_task.py @@ -43,24 +43,6 @@ class TabularClassificationTask(ClassificationTask): de="Tabellarische Klassifikation", zh="表格分类", ) - SCORING_PROFILES = { - "balanced": { - "description": "Balanced", - "weights": {"Accuracy": 0.3, "F1": 0.4, "ROCAUC": 0.3}, - }, - "detectPositives": { - "description": "Detect Positives", - "weights": {"Recall": 0.6, "F1": 0.3, "Precision": 0.1}, - }, - "avoidFalseAlarms": { - "description": "Avoid False Alarms", - "weights": {"Precision": 0.6, "F1": 0.3, "Recall": 0.1}, - }, - "probabilityQuality": { - "description": "Probability Quality", - "weights": {"ROCAUC": 0.5, "LogLoss": 0.5}, - }, - } metadata: dict = { "inputs_types": [Float, Integer, Categorical], "outputs_types": [Categorical], diff --git a/DashAI/back/tasks/text_classification_task.py b/DashAI/back/tasks/text_classification_task.py index c2ed7053a..9ea82bd92 100644 --- a/DashAI/back/tasks/text_classification_task.py +++ b/DashAI/back/tasks/text_classification_task.py @@ -22,25 +22,6 @@ class TextClassificationTask(ClassificationTask): a predicted class label for each sample. """ - SCORING_PROFILES = { - "text_balanced": { - "description": "Balanced", - "weights": {"Accuracy": 0.3, "F1": 0.4, "ROCAUC": 0.3}, - }, - "text_detectPositives": { - "description": "Detect Positives", - "weights": {"Recall": 0.6, "F1": 0.3, "Precision": 0.1}, - }, - "text_avoidFalseAlarms": { - "description": "Avoid False Alarms", - "weights": {"Precision": 0.6, "F1": 0.3, "Recall": 0.1}, - }, - "text_probabilityQuality": { - "description": "Probability Quality", - "weights": {"ROCAUC": 0.5, "LogLoss": 0.5}, - }, - } - metadata: dict = { "inputs_types": [Text], "outputs_types": [Categorical], diff --git a/DashAI/back/tasks/translation_task.py b/DashAI/back/tasks/translation_task.py index cf421471d..467b2f349 100644 --- a/DashAI/back/tasks/translation_task.py +++ b/DashAI/back/tasks/translation_task.py @@ -24,17 +24,6 @@ class TranslationTask(BaseTask): COMPATIBLE_COMPONENTS = ["Bleu", "Chrf", "Ter"] - SCORING_PROFILES = { - "translation_quality": { - "description": "Translation Quality", - "weights": {"Bleu": 0.5, "Chrf": 0.5}, - }, - "translation_balanced": { - "description": "Translation Balanced", - "weights": {"Bleu": 0.4, "Chrf": 0.3, "Ter": 0.3}, - }, - } - metadata: dict = { "inputs_types": [Text], "outputs_types": [Text], diff --git a/DashAI/front/package.json b/DashAI/front/package.json index 915f700fa..826abe627 100644 --- a/DashAI/front/package.json +++ b/DashAI/front/package.json @@ -4,6 +4,8 @@ "private": true, "dependencies": { "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@mui/icons-material": "^7", diff --git a/DashAI/front/src/App.jsx b/DashAI/front/src/App.jsx index 903e514dc..46abcdeb6 100644 --- a/DashAI/front/src/App.jsx +++ b/DashAI/front/src/App.jsx @@ -61,6 +61,10 @@ function App() { path="/app/models/sessions/new/:taskName" element={} /> + } + /> } /> } /> { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("posts to the download endpoint and returns the job id", async () => { + (api.post as jest.Mock).mockResolvedValue({ data: { id: "job-1" } }); + + const result = await downloadComponent("OpusMtEnRoaTransformer"); + expect(api.post).toHaveBeenCalledWith( + "/v1/component/OpusMtEnRoaTransformer/download", + ); + expect(result).toEqual({ id: "job-1" }); + }); +}); diff --git a/DashAI/front/src/api/component.ts b/DashAI/front/src/api/component.ts index cc66da278..0e1a42fd7 100644 --- a/DashAI/front/src/api/component.ts +++ b/DashAI/front/src/api/component.ts @@ -54,3 +54,47 @@ export const getComponentById = async (id: string): Promise => { const response = await api.get(`/v1/component/${id}/`); return response.data; }; + +export const downloadComponent = async ( + name: string, +): Promise<{ id: string }> => { + const response = await api.post<{ id: string }>( + `/v1/component/${name}/download`, + ); + return response.data; +}; + +export const deleteComponentDownload = async (name: string): Promise => { + await api.delete(`/v1/component/${name}/download`); +}; + +export const getComponentDownloadStatus = async ( + name: string, +): Promise<{ downloaded: boolean; requires_download: boolean }> => { + const response = await api.get<{ + downloaded: boolean; + requires_download: boolean; + }>(`/v1/component/${name}/download`); + return response.data; +}; + +export interface RequiredDownload { + name: string; + display_name: string; + parent: string | null; + download_size_bytes: number | null; +} + +// Resolve which components a configuration still needs downloaded. Walks +// nested component parameters server-side (a component selected as another +// component's parameter) and optionally checks the parent model itself. +export const getRequiredDownloads = async ( + parameters: Record, + modelName?: string, +): Promise => { + const response = await api.post( + `/v1/component/downloads/required`, + { model_name: modelName ?? null, parameters }, + ); + return response.data; +}; diff --git a/DashAI/front/src/api/explainer.ts b/DashAI/front/src/api/explainer.ts index 1e9293350..1778f3f23 100644 --- a/DashAI/front/src/api/explainer.ts +++ b/DashAI/front/src/api/explainer.ts @@ -1,4 +1,5 @@ import api from "./api"; +import type { IArtifact } from "../types/artifact"; import type { IExplainer } from "../types/explainer"; export const getExplainers = async ( @@ -15,8 +16,8 @@ export const getExplainers = async ( export const getExplainerPlot = async ( explainerId: string = "", scope: string = "", -): Promise => { - const response = await api.get( +): Promise => { + const response = await api.get( `/v1/explainer/${scope}/plot/${explainerId}`, ); @@ -24,13 +25,11 @@ export const getExplainerPlot = async ( }; export const createGlobalExplainer = async ( - name: string, runId: number, explainerName: string, parameters: object, ): Promise => { const data = { - name, run_id: runId, explainer_name: explainerName, parameters, @@ -41,7 +40,6 @@ export const createGlobalExplainer = async ( }; export const createLocalExplainer = async ( - name: string, runId: number, explainerName: string, datasetId: string, @@ -50,7 +48,6 @@ export const createLocalExplainer = async ( scope: object, ): Promise => { const data = { - name, run_id: runId, dataset_id: datasetId, explainer_name: explainerName, @@ -77,6 +74,14 @@ export const validateDataset = async ( return response.data; }; +export const getValidDatasets = async (runId: number): Promise => { + const response = await api.post<{ valid_dataset_ids: number[] }>( + "/v1/explainer/local/valid-datasets", + { run_id: runId }, + ); + return response.data.valid_dataset_ids; +}; + export const deleteExplainer = async ( scope: string, id: string, @@ -84,3 +89,27 @@ export const deleteExplainer = async ( const response = await api.delete(`/v1/explainer/${scope}/${id}`); return response.data; }; + +export const saveExplainerPlotOverride = async ( + scope: string, + explainerId: number, + index: number, + figure: unknown, +): Promise => { + const response = await api.put( + `/v1/explainer/${scope}/plot/${explainerId}/override`, + { index, figure }, + ); + return response.data; +}; + +export const resetExplainerPlotOverride = async ( + scope: string, + explainerId: number, + index: number, +): Promise => { + const response = await api.delete( + `/v1/explainer/${scope}/plot/${explainerId}/override/${index}`, + ); + return response.data; +}; diff --git a/DashAI/front/src/api/explorer.ts b/DashAI/front/src/api/explorer.ts index 7c39c4844..9f5561083 100644 --- a/DashAI/front/src/api/explorer.ts +++ b/DashAI/front/src/api/explorer.ts @@ -1,5 +1,6 @@ import api from "./api"; -import type { IExplorer, IExplorerResults } from "../types/explorer"; +import type { IArtifact } from "../types/artifact"; +import type { IExplorer } from "../types/explorer"; const explorerEndpoint = "/v1/explorer"; @@ -103,7 +104,7 @@ export const deleteExplorer = async (explorerId: string): Promise => { export const getExplorerResults = async ( explorerId: number, options: object = {}, -): Promise => { +): Promise => { const data = { options }; const response = await api.post( `${explorerEndpoint}/${explorerId}/results/`, diff --git a/DashAI/front/src/api/job.ts b/DashAI/front/src/api/job.ts index 684fd434d..b3cd69da4 100644 --- a/DashAI/front/src/api/job.ts +++ b/DashAI/front/src/api/job.ts @@ -82,15 +82,37 @@ export const enqueueDatasetJob = async ( export const enqueueExplainerJob = async ( explainerId: number, scope: string, + manualInputData?: object[], ): Promise => { - const data = { - job_type: "ExplainerJob", - kwargs: { explainer_id: explainerId, explainer_scope: scope }, + const formData = new FormData(); + + // When the local explainer explains manually-entered inputs, the rows travel + // in the job kwargs (like manual predictions). File values are swapped for a + // placeholder key and the File is appended separately so the job endpoint can + // reattach it. + const kwargs: Record = { + explainer_id: explainerId, + explainer_scope: scope, }; - const formData = new FormData(); - formData.append("job_type", data.job_type); - formData.append("kwargs", JSON.stringify(data.kwargs)); + if (manualInputData) { + kwargs.manual_input_data = manualInputData.map((obj, i) => { + const cleanObj: Record = {}; + Object.entries(obj).forEach(([key, value]) => { + if (value instanceof File) { + const fieldName = `file_${i}_${key}`; + formData.append(fieldName, value); + cleanObj[key] = fieldName; + } else { + cleanObj[key] = value; + } + }); + return cleanObj; + }); + } + + formData.append("job_type", "ExplainerJob"); + formData.append("kwargs", JSON.stringify(kwargs)); const response = await api.post("/v1/job/", formData, { headers: { diff --git a/DashAI/front/src/api/predict.ts b/DashAI/front/src/api/predict.ts index 56a174bbc..c38555a3e 100644 --- a/DashAI/front/src/api/predict.ts +++ b/DashAI/front/src/api/predict.ts @@ -3,11 +3,18 @@ import api from "./api"; import { IParamsFilter } from "../types/predict"; const predictEndpoint = "/v1/predict"; -export const filterDatasets = async (requestData: IParamsFilter) => { - const response = await api.get(`${predictEndpoint}/filter_datasets`, { - params: requestData, - }); - return response.data; +// Returns only the ids of datasets compatible with the run's model - the +// backend checks every dataset's schema in one request, so the frontend can +// filter an already-fetched dataset list by id instead of fetching per-dataset +// info for every candidate up front. +export const filterDatasets = async ( + requestData: IParamsFilter, +): Promise => { + const response = await api.get<{ valid_dataset_ids: number[] }>( + `${predictEndpoint}/filter_datasets`, + { params: requestData }, + ); + return response.data.valid_dataset_ids; }; export const downloadPredict = async (prediction_id: string) => { diff --git a/DashAI/front/src/api/session.ts b/DashAI/front/src/api/session.ts index a08e4d534..4f1ed1602 100644 --- a/DashAI/front/src/api/session.ts +++ b/DashAI/front/src/api/session.ts @@ -31,7 +31,7 @@ export const updateGenerativeSession = async ({ formData, }: { id: string; - formData: { name?: string; task_name?: string }; + formData: { name?: string; task_name?: string; model_name?: string }; }): Promise => { const response = await api.patch(`/v1/generative-session/${id}`, null, { params: formData, diff --git a/DashAI/front/src/components/DatasetVisualization.jsx b/DashAI/front/src/components/DatasetVisualization.jsx index e2e3c3663..cba261b06 100644 --- a/DashAI/front/src/components/DatasetVisualization.jsx +++ b/DashAI/front/src/components/DatasetVisualization.jsx @@ -7,12 +7,12 @@ import { Box, Alert, Divider, - Tabs, Tab, Tooltip, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { AddCircleOutline as AddIcon } from "@mui/icons-material"; +import PillTabs from "./shared/PillTabs"; import { getDatasetInfo, getDatasetFile, @@ -358,44 +358,8 @@ export default function DatasetVisualization({ /> {/* Tabs */} - setTab(newValue)} > @@ -432,7 +396,7 @@ export default function DatasetVisualization({ Object.keys(datasetInfo.correlations).length === 0 } /> - + {/* Divider */} diff --git a/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx b/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx index 5750881f8..c46b51fb3 100644 --- a/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx +++ b/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import FormTooltip from "../FormTooltip"; import { Input } from "./InputStyles"; import { + Box, IconButton, MenuItem, Dialog, @@ -15,6 +16,7 @@ import { } from "@mui/material"; import SettingsIcon from "@mui/icons-material/Settings"; import Subform from "../Subform"; +import ComponentDownloadControl from "../../models/model/ComponentDownloadControl"; import { getDefaultValues } from "../../../utils/values"; import { getModelSchema as getModelSchemaRequest, @@ -140,6 +142,28 @@ function ClassInput({ + {(() => { + const selectedComponent = options.find( + (option) => option.name === selectedOption, + ); + return selectedComponent?.metadata?.requires_download ? ( + + + setOptions((prev) => + prev.map((option) => + option.name === selectedComponent.name + ? { ...option, downloaded: isDownloaded } + : option, + ), + ) + } + /> + + ) : null; + })()} + {/* Button to show the modal that contains the subform */} diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index b12423a2c..c2a399dec 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -18,6 +18,7 @@ import { Check as CheckIcon, } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; const ALL_CATEGORY = "All"; const SEARCH_THRESHOLD = 10; @@ -41,8 +42,9 @@ function ComponentSelector({ flat = false, tourDataFor = null, tourDataMatchFn = null, + onDownloadChange = null, }) { - const { t } = useTranslation("custom"); + const { t } = useTranslation(["custom", "common"]); const [search, setSearch] = useState(""); const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY); @@ -107,6 +109,8 @@ function ComponentSelector({ const renderCard = (component) => { const isSelected = selected?.name === component.name; const icon = getIcon?.(component); + const requiresDownload = Boolean(component.metadata?.requires_download); + const needsDownload = requiresDownload && !component.downloaded; const isCsvComponent = tourDataFor && (tourDataMatchFn @@ -121,56 +125,80 @@ function ComponentSelector({ sx={{ p: 3, display: "flex", + flexDirection: "column", gap: 3, - alignItems: "flex-start", cursor: "pointer", border: 1, borderColor: isSelected ? "primary.main" : "divider", bgcolor: isSelected ? "action.selected" : "background.paper", transition: "border-color 0.15s, background 0.15s", - "&:hover": { borderColor: "secondary.main" }, + "&:hover": { + borderColor: "secondary.main", + }, }} > - {icon && ( - - {icon} + {/* Dim only the card content while a download is required, so the + download control below keeps its normal color (CSS opacity on the + card would otherwise cap the button's opacity too). */} + + {icon && ( + + {icon} + + )} + + + {getLabel(component)} + + + {getDescription(component, t("noDescriptionAvailable"))} + - )} - - - {getLabel(component)} - - - {getDescription(component, t("noDescriptionAvailable"))} - + {isSelected && ( + + )} - {isSelected && ( - + {requiresDownload && ( + e.stopPropagation()}> + + onDownloadChange?.(component, isDownloaded) + } + /> + )} ); @@ -358,7 +386,9 @@ ComponentSelector.propTypes = { emptyText: PropTypes.string, getIcon: PropTypes.func, flat: PropTypes.bool, + tourDataFor: PropTypes.string, tourDataMatchFn: PropTypes.func, + onDownloadChange: PropTypes.func, }; export default ComponentSelector; diff --git a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx new file mode 100644 index 000000000..8ab89c2a2 --- /dev/null +++ b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx @@ -0,0 +1,116 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Divider, TablePagination } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +import { getDatasetFile } from "../../api/datasets"; +import LeanDatasetTable from "../shared/leanDatasetTable/LeanDatasetTable"; +import "../shared/leanDatasetTable/leanDatasetTable.css"; + +const ROWS_PER_PAGE = 10; + +/** + * Instance picker for a local explainer's explained rows. When the explainer + * stored its input rows as a dataset (datasetPath), it renders the shared + * dataset table (feature values, image thumbnails, pagination). Otherwise, for + * explainers computed before input rows were persisted, it falls back to a + * list of instance labels styled like the dataset table. Selecting a row calls + * onSelect with the instance index. + */ +export default function ExplainerInstanceTable({ + datasetPath = null, + titles, + selectedIndex, + onSelect, +}) { + const theme = useTheme(); + const [page, setPage] = useState(0); + + if (datasetPath) { + return ( + + getDatasetFile(datasetPath, fetchPageIndex, pageSize) + } + datasetPath={datasetPath} + initialPageSize={10} + enableFilters={false} + enableSearch={false} + enableColumnVisibility={false} + enableRowsPerPage={false} + showExportButton={false} + selectedRowIndex={selectedIndex} + onRowClick={(row, globalIndex) => onSelect(globalIndex)} + /> + ); + } + + const pageStart = page * ROWS_PER_PAGE; + const pageTitles = titles.slice(pageStart, pageStart + ROWS_PER_PAGE); + + return ( + +
+ + + {pageTitles.map((title, i) => { + const globalIndex = pageStart + i; + const isSelected = globalIndex === selectedIndex; + return ( + onSelect(globalIndex)} + style={{ + backgroundColor: isSelected + ? theme.palette.action.selected + : undefined, + }} + > + + + ); + })} + +
+ {title} +
+
+ + setPage(newPage)} + rowsPerPageOptions={[ROWS_PER_PAGE]} + labelRowsPerPage="" + slotProps={{ select: { sx: { display: "none" } } }} + /> +
+ ); +} + +ExplainerInstanceTable.propTypes = { + datasetPath: PropTypes.string, + titles: PropTypes.arrayOf(PropTypes.string).isRequired, + selectedIndex: PropTypes.number, + onSelect: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/explainers/ExplainerSourceToggle.jsx b/DashAI/front/src/components/explainers/ExplainerSourceToggle.jsx new file mode 100644 index 000000000..57d08a2b2 --- /dev/null +++ b/DashAI/front/src/components/explainers/ExplainerSourceToggle.jsx @@ -0,0 +1,41 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { ToggleButton, ToggleButtonGroup } from "@mui/material"; +import StorageIcon from "@mui/icons-material/Storage"; +import CreateIcon from "@mui/icons-material/Create"; +import { useTranslation } from "react-i18next"; + +/** + * Chooses where the instances to explain come from: an existing dataset or + * values typed in by hand. + */ +export default function ExplainerSourceToggle({ source, onChange }) { + const { t } = useTranslation(["explainers"]); + + return ( + { + if (value) onChange(value); + }} + size="small" + fullWidth + sx={{ mb: 4 }} + > + + + {t("explainers:label.sourceFromDataset")} + + + + {t("explainers:label.sourceManualInput")} + + + ); +} + +ExplainerSourceToggle.propTypes = { + source: PropTypes.oneOf(["dataset", "manual"]).isRequired, + onChange: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/explainers/ExplainersCard.jsx b/DashAI/front/src/components/explainers/ExplainersCard.jsx index 8d02386d3..a9600660d 100644 --- a/DashAI/front/src/components/explainers/ExplainersCard.jsx +++ b/DashAI/front/src/components/explainers/ExplainersCard.jsx @@ -1,25 +1,26 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Grid, Typography, IconButton, Paper, - Button, - Collapse, Box, CircularProgress, } from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import RunStatusDot from "../shared/RunStatusDot"; import DeleteIcon from "@mui/icons-material/Delete"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import PropTypes from "prop-types"; import ExplainersPlot from "./ExplainersPlot"; import { useNavigate } from "react-router-dom"; -import { deleteExplainer } from "../../api/explainer"; +import { + deleteExplainer, + saveExplainerPlotOverride, + resetExplainerPlotOverride, +} from "../../api/explainer"; import { useTranslation } from "react-i18next"; -import { getComponentById } from "../../api/component"; const RUNNING_STATUSES = [1, 2]; // Delivered or Started @@ -33,18 +34,17 @@ export default function ExplainersCard({ scope, onDelete, compact = false, + displayName = null, + cacheEntry = null, + onCacheUpdate = null, + isHighlighted = false, }) { + const theme = useTheme(); const [open, setOpen] = useState(false); - const expandedStorageKey = `explainer-${scope}-${explainer.id}-expanded`; - const [expanded, setExpanded] = useState(() => { - const saved = localStorage.getItem(expandedStorageKey); - return saved !== null ? JSON.parse(saved) : true; - }); - - useEffect(() => { - localStorage.setItem(expandedStorageKey, JSON.stringify(expanded)); - }, [expanded, expandedStorageKey]); - const [componentData, setComponentData] = useState(null); + const [localOverriddenIndexes, setLocalOverriddenIndexes] = useState([]); + const overriddenIndexes = cacheEntry + ? (cacheEntry.overriddenIndexes ?? []) + : localOverriddenIndexes; const { t } = useTranslation(["explainers"]); const isRunning = RUNNING_STATUSES.includes(explainer.status); @@ -72,21 +72,47 @@ export default function ExplainersCard({ } }; - useEffect(() => { - getComponentById(explainer.explainer_name) - .then((data) => { - setComponentData(data); - }) - .catch((error) => { - console.error("Error fetching component data:", error); - }); - }, [explainer.explainer_name]); + const handleSaveOverride = async (index, figure) => { + await saveExplainerPlotOverride(scope, explainer.id, index, figure); + const next = overriddenIndexes.includes(index) + ? overriddenIndexes + : [...overriddenIndexes, index]; + if (onCacheUpdate) onCacheUpdate({ overriddenIndexes: next }); + else setLocalOverriddenIndexes(next); + }; + + const handleResetOverride = async (index) => { + await resetExplainerPlotOverride(scope, explainer.id, index); + const next = overriddenIndexes.filter((i) => i !== index); + if (onCacheUpdate) onCacheUpdate({ overriddenIndexes: next }); + else setLocalOverriddenIndexes(next); + }; if (compact) { return ( <> - - + + - + - {componentData - ? componentData.display_name - : plotName(explainer.explainer_name)} - - {explainer.name} - + {displayName || plotName(explainer.explainer_name)} + - {isRunning && } ) : ( - - - - - - - + {/* Reserved slot for the future "generate story" action button. + Kept hidden until that feature lands. */} +
+ )} @@ -186,9 +205,6 @@ export default function ExplainersCard({ {plotName(explainer.explainer_name)} - - {t("explainers:label.forExplainer", { name: explainer.name })} - {isRunning && } @@ -246,4 +262,12 @@ ExplainersCard.propTypes = { scope: PropTypes.string.isRequired, onDelete: PropTypes.func, compact: PropTypes.bool, + displayName: PropTypes.string, + cacheEntry: PropTypes.shape({ + items: PropTypes.array, + overriddenIndexes: PropTypes.arrayOf(PropTypes.number), + selectedGroups: PropTypes.object, + }), + onCacheUpdate: PropTypes.func, + isHighlighted: PropTypes.bool, }; diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 1c3ac555f..99c8f0cec 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -1,159 +1,316 @@ -import { React, useEffect, useMemo, useState } from "react"; -import { - FormControl, - InputLabel, - MenuItem, - Select, - CircularProgress, - Box, -} from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import Plot from "react-plotly.js"; +import { React, useEffect, useState } from "react"; +import { CircularProgress, Box } from "@mui/material"; import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; import { getExplainerPlot as getExplainerPlotRequest } from "../../api/explainer"; import { useTranslation } from "react-i18next"; -import { applyThemeToLayout } from "../../utils/plotlyTheme"; +import ArtifactViewer from "../shared/ArtifactViewer"; +import ExplainerInstanceTable from "./ExplainerInstanceTable"; -export default function ExplainersPlot({ explainer, scope }) { - const { enqueueSnackbar } = useSnackbar(); - const theme = useTheme(); - const [explainersPlots, setExplainersPlots] = useState([]); - const [currentPlot, setCurrentPlot] = useState(0); - const [loading, setLoading] = useState(true); - const isLocal = scope === "local"; +/** Wrap legacy plotly JSON strings as plotly artifacts; pass typed dicts through. */ +function parseExplanationArtifacts(items) { + return items.map((item) => + typeof item === "string" + ? { type: "plotly", payload: item, title: null, role: "explanation" } + : item, + ); +} + +/** Build the onSaveEdit/onResetEdit/canReset props shared by every leaf. */ +function leafProps( + artifact, + { onSaveOverride, onResetOverride, overriddenIndexes }, +) { + return { + canReset: overriddenIndexes.includes(artifact.index), + onSaveEdit: onSaveOverride + ? (figure) => onSaveOverride(artifact.index, figure) + : null, + onResetEdit: onResetOverride ? () => onResetOverride(artifact.index) : null, + }; +} + +/** + * Lay out a batch of leaf artifacts: the first artifact fills the row beside + * whatever `leading` element is passed (a selector, or nothing); any further + * artifacts stack below at full width, most recent first. `siblings` is the + * full artifact list of the batch so the fullscreen viewer can navigate + * between them. + */ +function ArtifactBatch({ + artifacts, + siblings, + ctx, + leading = null, + leadingFlex, + leadingMinWidth = 0, + siblingOffset = 0, +}) { + // Key by position within the batch, not by artifact.index: switching the + // selected group then reuses the same viewer/Plot instance at each slot and + // updates it in place (Plotly diffs) instead of unmounting the tall old plot + // and mounting a new one, which briefly collapses page height and makes the + // window scroll up. + // + // siblingIndex maps this leaf into `siblings` (which may span every group, + // not just this batch) via siblingOffset, so the fullscreen viewer can page + // across groups even when each group has a single artifact. + const renderLeaf = (artifact, i) => ( + + ); + + const [firstArtifact, ...rest] = artifacts; + const stacked = rest.map((artifact, i) => ({ artifact, i: i + 1 })).reverse(); + + return ( + + + {leading && ( + + {leading} + + )} + {renderLeaf(firstArtifact, 0)} + + {stacked.map(({ artifact, i }) => renderLeaf(artifact, i))} + + ); +} + +ArtifactBatch.propTypes = { + artifacts: PropTypes.array.isRequired, + siblings: PropTypes.array.isRequired, + ctx: PropTypes.object.isRequired, + leading: PropTypes.node, + leadingFlex: PropTypes.string, + leadingMinWidth: PropTypes.number, + siblingOffset: PropTypes.number, +}; + +/** + * Render a GroupedArtifacts item: a selector listing every group, beside the + * selected group's first artifact (with the rest stacked below). Holds its own + * selection state, so multiple selectors on one card are independent. + * + * The selector widget depends on `datasetPath`: local explainers pass the + * explained rows dataset path so the picker shows the actual instance feature + * values (the row index selects the group); global explainers omit it and get + * a plain title list. + */ +function GroupedArtifactsView({ + grouped, + ctx, + datasetPath = null, + selected: selectedProp = null, + onSelect = null, +}) { const { t } = useTranslation(["explainers"]); + const [localSelected, setLocalSelected] = useState(0); + const selected = selectedProp ?? localSelected; + const setSelected = onSelect ?? setLocalSelected; + const groups = grouped.groups ?? []; + if (groups.length === 0) return null; + + const group = groups[selected] ?? groups[0]; + const titles = groups.map( + (g, i) => + g.title ?? t("explainers:label.instanceNumber", { number: i + 1 }), + ); + const wide = Boolean(datasetPath); + + // Fullscreen navigation spans every group's artifacts (flattened), so the + // viewer can page across groups even when each group has a single artifact. + // The selected group's artifacts occupy the slice starting at `offset`. + const allArtifacts = groups.flatMap((g) => g.artifacts); + const offset = groups + .slice(0, selected) + .reduce((n, g) => n + g.artifacts.length, 0); - const themedLayout = useMemo(() => { - if (!explainersPlots[currentPlot]) return {}; - return applyThemeToLayout(explainersPlots[currentPlot].layout, theme); - }, [explainersPlots, currentPlot, theme]); - function parseExplanationPlot(explanation) { - const formattedPlot = JSON.parse(JSON.stringify(explanation)); - return formattedPlot.map(JSON.parse); + // Rendered directly (no height cap): ExplainerInstanceTable's root is + // height:100%, so it fills the stretched batch cell and matches the height + // of the first artifact beside it, scrolling internally when long. + const selector = ( + + ); + + return ( + + ); +} + +GroupedArtifactsView.propTypes = { + grouped: PropTypes.object.isRequired, + ctx: PropTypes.object.isRequired, + datasetPath: PropTypes.string, + selected: PropTypes.number, + onSelect: PropTypes.func, +}; + +/** + * Render one top level response item: a "grouped" selector + * (`GroupedArtifactsView`) or a plain leaf artifact (shown alone at full + * width). `datasetPath` is forwarded to grouped items so local explainers get + * the dataset row picker. + */ +function renderItem(item, ctx, datasetPath = null, selection = null) { + if (item.type === "grouped") { + return ( + + ); } + return ; +} + +export default function ExplainersPlot({ + explainer, + scope, + onSaveOverride = null, + onResetOverride = null, + overriddenIndexes = [], + cacheEntry = null, + onCacheUpdate = null, +}) { + const { enqueueSnackbar } = useSnackbar(); + const cachedItems = cacheEntry ? cacheEntry.items : null; + const [items, setItems] = useState(() => cachedItems ?? []); + const [loading, setLoading] = useState(() => cachedItems == null); + const { t } = useTranslation(["explainers"]); + const isLocal = scope === "local"; + const datasetPath = isLocal ? explainer.input_dataset_path : null; const getExplainerPlot = async () => { setLoading(true); try { - const explainersPlots = await getExplainerPlotRequest( - explainer.id, - scope, - ); - if (!explainersPlots || explainersPlots.length === 0) { - setExplainersPlots([]); - setCurrentPlot(0); - enqueueSnackbar(t("explainers:error.noData"), { - variant: "warning", - }); + const response = await getExplainerPlotRequest(explainer.id, scope); + if (!response || response.length === 0) { + setItems([]); + if (onCacheUpdate) onCacheUpdate({ items: [] }); + enqueueSnackbar(t("explainers:error.noData"), { variant: "warning" }); } else { - const parsedExplainersPlot = parseExplanationPlot(explainersPlots); - setExplainersPlots(parsedExplainersPlot); - // Reset currentPlot when data updates to avoid stale index - setCurrentPlot(0); + const parsed = parseExplanationArtifacts(response); + setItems(parsed); + if (onCacheUpdate) onCacheUpdate({ items: parsed }); } } catch (error) { - setExplainersPlots([]); - setCurrentPlot(0); + setItems([]); + if (onCacheUpdate) onCacheUpdate({ items: [] }); enqueueSnackbar(t("explainers:error.fetchExplainers"), { variant: "error", }); - if (error.response) { - console.error("Response error:", error.message); - } else if (error.request) { - console.error("Request error", error.request); - } else { - console.error("Unknown Error", error.message); - } + console.error(error); } finally { setLoading(false); } }; useEffect(() => { - if (explainer.status === 3) { - getExplainerPlot(); + if (explainer.status !== 3) return; + // Cache hit: reuse fetched artifacts, skip the network entirely so a card + // scrolled back into view does not refetch. + if (cacheEntry && cacheEntry.items != null) { + setItems(cacheEntry.items); + setLoading(false); + return; + } + getExplainerPlot(); + }, [explainer.id, explainer.status, scope]); + + if (loading || explainer.status !== 3) { + if (explainer.status === 4) { + return {t("explainers:error.explainerFailed")}; } - }, [explainer.id, explainer.status]); + return ( + + + + ); + } + + if (items.length === 0) { + return {t("explainers:error.noData")}; + } + const ctx = { onSaveOverride, onResetOverride, overriddenIndexes }; + + // Every top level item renders continuously: a plain artifact at full width, + // a "grouped" item as its own self contained selector. Local explainers pass + // the explained rows dataset path so their grouped selector shows the + // instance feature values instead of plain labels. return ( - {!loading && isLocal && explainersPlots.length > 0 && ( - - Select an instance - - - )} - {!loading && explainer.status === 3 ? ( - explainersPlots.length > 0 && explainersPlots[currentPlot] ? ( - - ) : ( - {t("explainers:error.noData")} - ) - ) : explainer.status === 4 ? ( - {t("explainers:error.explainerFailed")} - ) : ( - - + {items.map((item, i) => ( + + {renderItem(item, ctx, datasetPath, { + selected: cacheEntry ? (cacheEntry.selectedGroups?.[i] ?? 0) : null, + onSelect: onCacheUpdate + ? (value) => + onCacheUpdate({ + selectedGroups: { + ...(cacheEntry?.selectedGroups ?? {}), + [i]: value, + }, + }) + : null, + })} - )} + ))} ); } ExplainersPlot.propTypes = { explainer: PropTypes.shape({ - explainer_name: PropTypes.string, id: PropTypes.number, - parameters: PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - ), status: PropTypes.number, - runId: PropTypes.number, - explanationPath: PropTypes.string, - plot_path: PropTypes.string, - name: PropTypes.string, - created: PropTypes.string, + input_dataset_path: PropTypes.string, }).isRequired, scope: PropTypes.string.isRequired, + onSaveOverride: PropTypes.func, + onResetOverride: PropTypes.func, + overriddenIndexes: PropTypes.arrayOf(PropTypes.number), + cacheEntry: PropTypes.shape({ + items: PropTypes.array, + selectedGroups: PropTypes.object, + }), + onCacheUpdate: PropTypes.func, }; diff --git a/DashAI/front/src/components/explainers/ExplainersSidebar.jsx b/DashAI/front/src/components/explainers/ExplainersSidebar.jsx new file mode 100644 index 000000000..71e354c10 --- /dev/null +++ b/DashAI/front/src/components/explainers/ExplainersSidebar.jsx @@ -0,0 +1,222 @@ +import React, { useState, useEffect, useCallback } from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, TextField, CircularProgress } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { Search as SearchIcon } from "@mui/icons-material"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import SideBar from "../threeSectionLayout/panelContainers/SideBar"; +import { getComponents } from "../../api/component"; +import ModelListItem from "../models/model/ModelListItem"; +import InlineExplainerCreator from "./InlineExplainerCreator"; +import { useModels } from "../models/ModelsContext"; + +const matchesQuery = (component, query) => + (component.display_name || component.name).toLowerCase().includes(query) || + (component.metadata?.description || "").toLowerCase().includes(query); + +/** + * Right-side panel shown while the model detail view is on its Explainers + * tab. Lists the global/local explainers compatible with the session's task + * (mirroring the add-models sidebar); clicking one opens the explainer + * creation stepper with that explainer preselected. + */ +export default function ExplainersSidebar({ run, session, onCreated }) { + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + const { t } = useTranslation(["models", "explainers"]); + + const [globalExplainers, setGlobalExplainers] = useState([]); + const [localExplainers, setLocalExplainers] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [loading, setLoading] = useState(false); + const { explainerToCreate, openExplainerCreator, closeExplainerCreator } = + useModels(); + + const taskName = session?.task_name; + const modelName = run?.model_name; + + const fetchExplainers = useCallback(async () => { + if (!taskName) return; + try { + setLoading(true); + + const fetchScope = async (explainerType) => { + const [taskRelated, modelRelated] = await Promise.all([ + getComponents({ + selectTypes: [explainerType], + relatedComponent: taskName, + }), + modelName + ? getComponents({ + selectTypes: [explainerType], + relatedComponent: modelName, + }) + : Promise.resolve([]), + ]); + const seen = new Set(); + return [...taskRelated, ...modelRelated] + .filter((obj) => { + if (seen.has(obj.name)) return false; + seen.add(obj.name); + return true; + }) + .filter((obj) => !obj.name.startsWith("Fit")); + }; + + const [globalResponse, localResponse] = await Promise.all([ + fetchScope("GlobalExplainer"), + fetchScope("LocalExplainer"), + ]); + setGlobalExplainers(globalResponse); + setLocalExplainers(localResponse); + } catch (error) { + console.error("Error fetching explainers:", error); + enqueueSnackbar(t("explainers:error.fetchExplainers"), { + variant: "error", + }); + } finally { + setLoading(false); + } + }, [taskName, modelName, enqueueSnackbar, t]); + + useEffect(() => { + fetchExplainers(); + }, [fetchExplainers]); + + const query = searchQuery.trim().toLowerCase(); + const filteredGlobal = query + ? globalExplainers.filter((expl) => matchesQuery(expl, query)) + : globalExplainers; + const filteredLocal = query + ? localExplainers.filter((expl) => matchesQuery(expl, query)) + : localExplainers; + + const renderSection = (title, explainers, scope) => ( + + + {title} + + {explainers.length === 0 ? ( + + {searchQuery + ? t("models:label.noExplainersMatchSearch") + : t("models:label.noCompatibleExplainersFound")} + + ) : ( + + {explainers.map((explainer) => ( + + openExplainerCreator({ scope, name: explainer.name }) + } + /> + ))} + + )} + + ); + + return ( + + + + {t("models:label.availableExplainers")} + + + + + setSearchQuery(e.target.value)} + slotProps={{ + input: { + startAdornment: ( + + ), + }, + }} + /> + + + + {loading ? ( + + + + ) : ( + <> + {renderSection( + t("models:label.globalExplainers"), + filteredGlobal, + "global", + )} + {renderSection( + t("models:label.localExplainers"), + filteredLocal, + "local", + )} + + )} + + + {explainerToCreate && ( + + )} + + ); +} + +ExplainersSidebar.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number.isRequired, + }).isRequired, + session: PropTypes.shape({ + task_name: PropTypes.string, + }), + onCreated: PropTypes.func, +}; diff --git a/DashAI/front/src/components/explainers/ExplanationInfo.jsx b/DashAI/front/src/components/explainers/ExplanationInfo.jsx new file mode 100644 index 000000000..2860c771d --- /dev/null +++ b/DashAI/front/src/components/explainers/ExplanationInfo.jsx @@ -0,0 +1,62 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, Chip, Stack, Typography } from "@mui/material"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import { useTranslation } from "react-i18next"; + +/** + * Read only summary of the model's input and target columns, shown while + * choosing the data to explain so the user knows which features feed the model. + */ +export default function ExplanationInfo({ inputColumns, outputColumns }) { + const { t } = useTranslation(["explainers"]); + + if ( + (!inputColumns || inputColumns.length === 0) && + (!outputColumns || outputColumns.length === 0) + ) { + return null; + } + + return ( + `${theme.palette.primary.main}14`, + }} + > + + + + {t("explainers:label.explanationInfo")} + + + + + {t("explainers:label.inputColumns")} + + + {(inputColumns ?? []).map((col) => ( + + ))} + + + + {t("explainers:label.targetColumn")} + + + {(outputColumns ?? []).map((col) => ( + + ))} + + + ); +} + +ExplanationInfo.propTypes = { + inputColumns: PropTypes.arrayOf(PropTypes.string), + outputColumns: PropTypes.arrayOf(PropTypes.string), +}; diff --git a/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx b/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx index 753d80897..168aa54b9 100644 --- a/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx +++ b/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx @@ -21,33 +21,22 @@ import { useTranslation } from "react-i18next"; import { createGlobalExplainer as createGlobalExplainerRequest, createLocalExplainer as createLocalExplainerRequest, - getExplainers, } from "../../api/explainer"; import { enqueueExplainerJob as enqueueExplainerJobRequest } from "../../api/job"; import { startJobPolling } from "../../utils/jobPoller"; import TimestampWrapper from "../shared/TimestampWrapper"; import { TIMESTAMP_KEYS } from "../../constants/timestamp"; -import { generateSequentialName } from "../../utils/nameGenerator"; import ConfigureExplainerStep from "./ConfigureExplainerStep"; import SelectDatasetStep from "./SelectDatasetStep"; import SetNameAndExplainerStep from "./SetNameAndExplainerStep"; const SNACKBAR_AUTO_HIDE_MS = 5000; -const getNextExplainerName = (scope, existingExplainers = []) => { - const { defaultName } = generateSequentialName({ - base: scope === "local" ? "Explainer_local" : "Explainer_global", - items: existingExplainers, - getName: (explainer) => explainer?.name, - }); - - return defaultName; -}; - export default function InlineExplainerCreator({ open, scope, explainerConfig, + preselectedExplainer = null, onCreated, onCancel, }) { @@ -55,97 +44,78 @@ export default function InlineExplainerCreator({ const { t } = useTranslation(["explainers", "common"]); const formSubmitRef = useRef(null); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const isLocal = scope === "local"; + // With a preselected explainer the selection step is skipped entirely; the + // stepper starts at dataset selection (local) or parameter configuration. + const hasPreselected = Boolean(preselectedExplainer); const defaultNewExplainer = useMemo( () => isLocal ? { - name: "", run_id: runId, - explainer_name: null, - scope: { split: "test", percentage: 20 }, + explainer_name: preselectedExplainer ?? null, + scope: { + mode: "split", + split: "test", + percentage: 20, + shuffle: false, + }, dataset_id: null, parameters: null, fit_parameters: null, + manual_input: null, } : { - name: "", run_id: runId, - explainer_name: null, + explainer_name: preselectedExplainer ?? null, parameters: null, }, - [isLocal, runId], + [isLocal, runId, preselectedExplainer], ); - const steps = useMemo( - () => - isLocal - ? [ - t("explainers:label.selectExplainer"), - t("explainers:label.selectDataset"), - t("explainers:label.configureExplainerParameters"), - ] - : [ - t("explainers:label.selectExplainer"), - t("explainers:label.configureExplainerParameters"), - ], - [isLocal, t], - ); + const steps = useMemo(() => { + const stepLabels = []; + if (!hasPreselected) { + stepLabels.push(t("explainers:label.selectExplainer")); + } + if (isLocal) { + stepLabels.push(t("explainers:label.selectDataset")); + } + stepLabels.push(t("explainers:label.configureExplainerParameters")); + return stepLabels; + }, [isLocal, hasPreselected, t]); + + const datasetStepIndex = hasPreselected ? 0 : 1; + const configureStepIndex = (hasPreselected ? 0 : 1) + (isLocal ? 1 : 0); const [activeStep, setActiveStep] = useState(0); const [nextEnabled, setNextEnabled] = useState(false); const [newExpl, setNewExpl] = useState(defaultNewExplainer); - const [existingExplainers, setExistingExplainers] = useState([]); - const [existingExplainersLoaded, setExistingExplainersLoaded] = - useState(false); const [isLoading, setIsLoading] = useState(false); const resetState = () => { setActiveStep(0); setNewExpl(defaultNewExplainer); setNextEnabled(false); - setExistingExplainers([]); - setExistingExplainersLoaded(false); - }; - - const loadExistingExplainers = async () => { - try { - const explainers = await getExplainers(undefined, scope); - setExistingExplainers(explainers); - } catch (error) { - console.error("Error loading existing explainers:", error); - setExistingExplainers([]); - } finally { - setExistingExplainersLoaded(true); - } }; useEffect(() => { - if (open) { - setExistingExplainersLoaded(false); - loadExistingExplainers(); - return; - } - - resetState(); + if (!open) resetState(); }, [open]); - useEffect(() => { - if (!open || !existingExplainersLoaded || newExpl.name.trim()) { - return; - } - - setNewExpl((prev) => ({ - ...prev, - name: getNextExplainerName(scope, existingExplainers), - })); - }, [open, existingExplainersLoaded, existingExplainers, newExpl.name, scope]); - const enqueueExplainerJob = async (explainerId) => { try { - const response = await enqueueExplainerJobRequest(explainerId, scope); + const manualInput = + isLocal && newExpl.scope?.mode === "manual" + ? newExpl.manual_input + : undefined; + const response = await enqueueExplainerJobRequest( + explainerId, + scope, + manualInput, + ); enqueueSnackbar( t( isLocal @@ -204,7 +174,6 @@ export default function InlineExplainerCreator({ const response = isLocal ? await createLocalExplainerRequest( - newExpl.name, newExpl.run_id, newExpl.explainer_name, newExpl.dataset_id, @@ -213,14 +182,12 @@ export default function InlineExplainerCreator({ newExpl.scope, ) : await createGlobalExplainerRequest( - newExpl.name, newExpl.run_id, newExpl.explainer_name, newExpl.parameters, ); await enqueueExplainerJob(response.id); - await loadExistingExplainers(); if (onCreated) onCreated(); return true; } catch (error) { @@ -262,7 +229,7 @@ export default function InlineExplainerCreator({ - {activeStep === 0 && ( + {!hasPreselected && activeStep === 0 && ( )} - {isLocal && activeStep === 1 && ( + {isLocal && activeStep === datasetStepIndex && ( )} - {((isLocal && activeStep === 2) || (!isLocal && activeStep === 1)) && ( + {activeStep === configureStepIndex && ( { + const node = nodeRef.current; + // Once shown, latch: stop observing and never unmount the plot. + if (!node || shown) return undefined; + // Give the plot a head start before it scrolls in. Kept within the list's + // increaseViewportBy so the item is already in the DOM when this fires. + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) setShown(true); + }, + { root: scrollRoot ?? null, rootMargin: CARD_PRELOAD_MARGIN }, + ); + observer.observe(node); + return () => observer.disconnect(); + }, [scrollRoot, shown]); + + useEffect(() => { + const node = nodeRef.current; + if (!shown || !node) return undefined; + const record = () => { + heightsRef.current[cacheKey] = node.offsetHeight; + }; + record(); + const observer = new ResizeObserver(record); + observer.observe(node); + return () => observer.disconnect(); + }, [shown, cacheKey, heightsRef]); + + const onCacheUpdate = useCallback( + (patch) => updateCacheEntry(cacheKey, patch), + [updateCacheEntry, cacheKey], + ); + + const knownHeight = heightsRef.current[cacheKey]; + return ( + + {shown ? ( + + ) : null} + + ); +}); + +LazyExplainerCard.propTypes = { + scrollRoot: PropTypes.instanceOf(Element), + heightsRef: PropTypes.shape({ current: PropTypes.object }).isRequired, + cacheKey: PropTypes.string.isRequired, + explainer: PropTypes.object.isRequired, + scope: PropTypes.string.isRequired, + displayName: PropTypes.string, + onDelete: PropTypes.func, + cacheEntry: PropTypes.object, + updateCacheEntry: PropTypes.func.isRequired, + isHighlighted: PropTypes.bool, +}; + +export default LazyExplainerCard; diff --git a/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx b/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx index 47f703038..36791161b 100644 --- a/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx +++ b/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx @@ -78,7 +78,7 @@ export default function NewGlobalExplainerModal({ const { enqueueSnackbar } = useSnackbar(); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const defaultNewGlobalExpl = { name: "", @@ -331,6 +331,7 @@ export default function NewGlobalExplainerModal({ setNextEnabled={setNextEnabled} scope={"Global"} taskName={taskName} + modelName={modelName} existingExplainers={existingGlobalExplainers} /> )} diff --git a/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx b/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx index cf13aefe1..58c337208 100644 --- a/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx +++ b/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx @@ -80,7 +80,7 @@ export default function NewLocalExplainerModal({ const { enqueueSnackbar } = useSnackbar(); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const defaultNewLocalExpl = { name: "", @@ -337,6 +337,7 @@ export default function NewLocalExplainerModal({ setNextEnabled={setNextEnabled} scope={"Local"} taskName={taskName} + modelName={modelName} existingExplainers={existingLocalExplainers} /> )} diff --git a/DashAI/front/src/components/explainers/RowSelectionModeToggle.jsx b/DashAI/front/src/components/explainers/RowSelectionModeToggle.jsx new file mode 100644 index 000000000..daca8f048 --- /dev/null +++ b/DashAI/front/src/components/explainers/RowSelectionModeToggle.jsx @@ -0,0 +1,40 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { ToggleButton, ToggleButtonGroup } from "@mui/material"; +import TuneIcon from "@mui/icons-material/Tune"; +import ChecklistIcon from "@mui/icons-material/Checklist"; +import { useTranslation } from "react-i18next"; + +/** + * Chooses how rows are picked from the selected dataset: a percentage of the + * chosen split, or rows marked by hand in the table. + */ +export default function RowSelectionModeToggle({ mode, onChange }) { + const { t } = useTranslation(["explainers"]); + + return ( + { + if (value) onChange(value); + }} + size="small" + fullWidth + > + + + {t("explainers:label.rowModePercentage")} + + + + {t("explainers:label.rowModeManual")} + + + ); +} + +RowSelectionModeToggle.propTypes = { + mode: PropTypes.oneOf(["percentage", "manual"]).isRequired, + onChange: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/explainers/SelectDatasetStep.jsx b/DashAI/front/src/components/explainers/SelectDatasetStep.jsx index 4bf53a961..0f23784aa 100644 --- a/DashAI/front/src/components/explainers/SelectDatasetStep.jsx +++ b/DashAI/front/src/components/explainers/SelectDatasetStep.jsx @@ -1,34 +1,46 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { Alert, AlertTitle, - Grid, + Box, + Divider, + FormControl, + FormControlLabel, Link, - Paper, + Radio, + RadioGroup, + Slider, + Stack, + Switch, + TextField, Typography, } from "@mui/material"; -import { - MaterialReactTable, - useMaterialReactTable, -} from "material-react-table"; -import { useTheme } from "@mui/material/styles"; import { useSnackbar } from "notistack"; import { Link as RouterLink } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; -import { useTableLocalization } from "../../utils/useTableLocalization"; import { getDatasets as getDatasetsRequest, getDatasetInfo, + getDatasetFile, + getDatasetTypes, + getDatasetTypesByFilePath, + getDatasetSample, } from "../../api/datasets"; -import { validateDataset as validateDatasetRequest } from "../../api/explainer"; +import { getValidDatasets as getValidDatasetsRequest } from "../../api/explainer"; import { getRunById } from "../../api/run"; import { getModelSessionById } from "../../api/modelSession"; -import { formatDate } from "../../utils"; -import { SplitSelector } from "./SplitSelector"; +import LeanDatasetTable from "../shared/leanDatasetTable/LeanDatasetTable"; +import ManualInput from "../predictions/ManualInput"; import NoteBox from "../notebooks/NoteBox"; +import DatasetAutocomplete from "../notebooks/notebookCreation/DatasetAutocomplete"; +import ExplainerSourceToggle from "./ExplainerSourceToggle"; +import RowSelectionModeToggle from "./RowSelectionModeToggle"; +import ExplanationInfo from "./ExplanationInfo"; + +const SPLIT_VALUES = ["train", "test", "val", "all"]; export default function SelectDatasetStep({ newExpl, @@ -36,241 +48,404 @@ export default function SelectDatasetStep({ setNextEnabled, }) { const { enqueueSnackbar } = useSnackbar(); - const [loading, setLoading] = useState(true); + const { t } = useTranslation(["explainers", "common", "datasets"]); + + // Where the instances to explain come from. + const [source, setSource] = useState("dataset"); + + // Dataset source state. const [datasets, setDatasets] = useState([]); - const [selectedDatasetId, setSelectedDatasetId] = useState(false); - const [isValidDataset, setIsValidDataset] = useState(false); + const [loadingDatasets, setLoadingDatasets] = useState(true); const [requestError, setRequestError] = useState(false); + const [selectedDataset, setSelectedDataset] = useState(null); + const [isValidDataset, setIsValidDataset] = useState(false); const [totalRows, setTotalRows] = useState(0); - const [splits, setSplits] = useState({ + const [columnTypes, setColumnTypes] = useState({}); + + // Model session metadata (drives the explanation info and the manual form). + const [inputColumns, setInputColumns] = useState([]); + const [outputColumns, setOutputColumns] = useState([]); + const [trainingDatasetId, setTrainingDatasetId] = useState(null); + const [splitFractions, setSplitFractions] = useState({ train: 0, test: 0, validation: 0, all: 1, }); - const { t } = useTranslation(["explainers", "common"]); - const theme = useTheme(); - const localization = useTableLocalization(); - - const columns = useMemo( - () => [ - { - accessorKey: "name", - header: "Name", - }, - { - accessorKey: "created", - header: "Created", - Cell: ({ cell }) => formatDate(cell.getValue()), - }, - { - accessorKey: "last_modified", - header: "Last modified", - Cell: ({ cell }) => formatDate(cell.getValue()), - }, - ], - [], - ); - const getDatasets = async () => { - setLoading(true); - try { - const datasets = await getDatasetsRequest(); - setDatasets(datasets); - } catch (error) { - enqueueSnackbar(t("explainers:error.fetchDatasets"), { - variant: "error", - }); - setRequestError(true); - if (error.response) { - console.error("Response error:", error.message); - } else if (error.request) { - console.error("Request error", error.request); - } else { - console.error("Unknown Error", error.message); - } - } finally { - setLoading(false); - } - }; - - const validateDataset = async () => { - try { - const validation = await validateDatasetRequest( - newExpl.run_id, - selectedDatasetId, - ); - setIsValidDataset(validation.dataset_status === "valid"); - if (validation.dataset_status === "invalid") { - enqueueSnackbar(t("explainers:error.invalidDataset"), { - variant: "error", - }); - } - } catch (error) { - enqueueSnackbar(t("explainers:error.validateDataset"), { - variant: "error", - }); - if (error.response) { - console.error("Response error:", error.message); - } else if (error.request) { - console.error("Request error", error.request); - } else { - console.error("Unknown Error", error.message); - } - } - }; + // Row selection controls (dataset source). + const [rowMode, setRowMode] = useState("percentage"); + const [split, setSplit] = useState("test"); + const [percentage, setPercentage] = useState(20); + const [shuffle, setShuffle] = useState(false); + const [selectedRowIndices, setSelectedRowIndices] = useState(() => new Set()); - const getTotalRows = async () => { - if (selectedDatasetId) { - try { - const datasetInfo = await getDatasetInfo(selectedDatasetId); - setTotalRows(datasetInfo.total_rows); - } catch { - console.error(`Error fetching dataset info for ${selectedDatasetId}`); - } - } - }; + // Manual input source state. + const [manualTypes, setManualTypes] = useState({}); + const [manualSample, setManualSample] = useState(null); + const [loadingManual, setLoadingManual] = useState(false); + const [manualRows, setManualRows] = useState([]); - // fetch datasets when the component is mounting - useEffect(() => { - getDatasets(); - }, []); + // ----- data fetching ------------------------------------------------- + // Only datasets compatible with the run's model (same input/output columns) + // can be explained. A single endpoint validates them all server-side and + // returns the valid ids, which we use to filter the dataset list. useEffect(() => { - getTotalRows(); - }, [selectedDatasetId]); + const fetchDatasets = async () => { + if (!newExpl.run_id) return; + setLoadingDatasets(true); + try { + const [all, validIds] = await Promise.all([ + getDatasetsRequest(), + getValidDatasetsRequest(newExpl.run_id), + ]); + const validIdSet = new Set(validIds.map(String)); + setDatasets(all.filter((ds) => validIdSet.has(String(ds.id)))); + } catch (error) { + enqueueSnackbar(t("explainers:error.fetchDatasets"), { + variant: "error", + }); + setRequestError(true); + console.error("Error fetching datasets", error); + } finally { + setLoadingDatasets(false); + } + }; + fetchDatasets(); + }, [newExpl.run_id]); - const getRuninfo = async () => { - if (newExpl.run_id) { + // Run -> model session metadata (input/target columns, splits, training id). + useEffect(() => { + const fetchRunInfo = async () => { + if (!newExpl.run_id) return; try { const run = await getRunById(newExpl.run_id); - const experiment = await getModelSessionById(run.model_session_id); - const splitsExperiment = JSON.parse(experiment.splits); - setSplits((prev) => ({ + const session = await getModelSessionById(run.model_session_id); + setInputColumns(session.input_columns ?? []); + setOutputColumns(session.output_columns ?? []); + setTrainingDatasetId(session.dataset_id); + const sessionSplits = JSON.parse(session.splits); + setSplitFractions((prev) => ({ ...prev, - train: splitsExperiment["train"], - test: splitsExperiment["test"], - validation: splitsExperiment["validation"], + train: sessionSplits.train, + test: sessionSplits.test, + validation: sessionSplits.validation, })); - } catch { - console.error(`Error fetching run info for ${newExpl.run_id}`); + } catch (error) { + console.error(`Error fetching run info for ${newExpl.run_id}`, error); } - } - }; - - useEffect(() => { - getRuninfo(); + }; + fetchRunInfo(); }, [newExpl.run_id]); - const handleRowClick = (row) => { - setSelectedDatasetId(row.original.id); - }; - - const datasetsTable = useMaterialReactTable({ - columns, - data: datasets, - muiTableBodyCellProps: { sx: { whiteSpace: "pre" } }, - enableRowSelection: false, - muiTableBodyRowProps: ({ row }) => ({ - onClick: () => handleRowClick(row), - sx: { - cursor: "pointer", - ...(row.original.id === selectedDatasetId && { - backgroundColor: `${theme.palette.primary.main}1F`, - borderLeft: `3px solid ${theme.palette.primary.main}`, - "&:hover td": { - backgroundColor: "transparent", - }, - }), - }, - }), - state: { isLoading: loading }, - enableGlobalFilter: false, - enableColumnFilters: false, - enableSorting: true, - enablePagination: true, - enableTopToolbar: false, - muiPaginationProps: { showRowsPerPage: false }, - initialState: { - pagination: { pageSize: 10, pageIndex: 0 }, - density: "compact", - }, - localization, - }); + // The list only contains valid datasets, so on selection just load the row + // count and column types needed by the rows table. + useEffect(() => { + const onDatasetSelected = async () => { + setSelectedRowIndices(new Set()); + if (!selectedDataset) { + setIsValidDataset(false); + return; + } + setIsValidDataset(true); + try { + const [info, types] = await Promise.all([ + getDatasetInfo(selectedDataset.id), + getDatasetTypesByFilePath(selectedDataset.file_path), + ]); + setTotalRows(info.total_rows ?? 0); + setColumnTypes(types ?? {}); + } catch (error) { + enqueueSnackbar(t("explainers:error.validateDataset"), { + variant: "error", + }); + console.error("Error loading dataset info", error); + } + }; + onDatasetSelected(); + }, [selectedDataset]); + // Load types + sample for the manual-input form (from the training dataset). useEffect(() => { - if (selectedDatasetId) { - validateDataset(); - } - }, [selectedDatasetId]); + const loadManualSchema = async () => { + if (source !== "manual" || !trainingDatasetId || manualSample) return; + setLoadingManual(true); + try { + const [types, sample] = await Promise.all([ + getDatasetTypes(trainingDatasetId), + getDatasetSample(trainingDatasetId), + ]); + setManualTypes(types); + setManualSample(sample); + } catch (error) { + enqueueSnackbar(t("explainers:error.validateDataset"), { + variant: "error", + }); + console.error("Error loading manual input schema", error); + } finally { + setLoadingManual(false); + } + }; + loadManualSchema(); + }, [source, trainingDatasetId]); + + // ----- write the scope / dataset_id / manual_input into newExpl ------ useEffect(() => { - if (isValidDataset && selectedDatasetId) { - setNewExpl((prevExpl) => ({ - ...prevExpl, - dataset_id: selectedDatasetId, + if (source === "manual") { + setNewExpl((prev) => ({ + ...prev, + dataset_id: trainingDatasetId, + scope: { mode: "manual" }, + manual_input: manualRows, })); - setNextEnabled(true); - } else { - setNextEnabled(false); + return; } - }, [isValidDataset, selectedDatasetId]); + + const scope = + rowMode === "manual" + ? { + mode: "rows", + row_indexes: [...selectedRowIndices].sort((a, b) => a - b), + } + : { mode: "split", split, percentage, shuffle }; + + setNewExpl((prev) => ({ + ...prev, + dataset_id: selectedDataset?.id ?? null, + scope, + manual_input: null, + })); + }, [ + source, + rowMode, + split, + percentage, + shuffle, + selectedRowIndices, + selectedDataset, + manualRows, + trainingDatasetId, + setNewExpl, + ]); + + // ----- gate the Next button ------------------------------------------ + + const datasetReady = Boolean(selectedDataset) && isValidDataset; + const dataValid = + source === "manual" + ? Boolean(manualSample) && manualRows.length > 0 + : rowMode === "manual" + ? datasetReady && selectedRowIndices.size > 0 + : datasetReady; + + useEffect(() => { + setNextEnabled(dataValid); + }, [dataValid]); + + // ----- handlers ------------------------------------------------------ + + const fractionFor = (splitValue) => + splitValue === "val" + ? splitFractions.validation + : (splitFractions[splitValue] ?? 0); + + const rowsInSplit = Math.round(totalRows * fractionFor(split)); + const rowsSelectedByPercentage = + percentage > 0 + ? Math.max(1, Math.round((percentage / 100) * rowsInSplit)) + : 1; + + const datasetFetchPage = (page, pageSize) => + getDatasetFile(selectedDataset.file_path, page, pageSize); + + // ----- render -------------------------------------------------------- return ( - - {/* Title and new datasets button */} - - - {t("explainers:label.selectDatasetToExplain")} - - - - {/* Datasets Table */} - - {datasets.length === 0 && !loading && !requestError && ( - - - - There are no datasets available. - Go to - - data tab - - to upload one first. - - - - )} - - - - - {selectedDatasetId && isValidDataset && ( - <> - { - setNewExpl((prevExpl) => ({ ...prevExpl, scope })); - }} + + + + {source === "manual" ? ( + + ) : ( + + + {t("explainers:label.selectDatasetToExplain")} + + + {datasets.length === 0 && !loadingDatasets && !requestError && ( + + + There are no datasets available. + Go to + + data tab + + to upload one first. + + + )} + + - - + + {datasetReady && ( + + + + {rowMode === "manual" && ( + + {t("explainers:label.markRowsToExplain")} + + )} + + + + )} + + {datasetReady && ( + + + {t("explainers:label.rowSelectionMode")} + + + + {rowMode === "percentage" ? ( + + + + {t("explainers:label.datasetSplit")} + + setSplit(e.target.value)} + sx={{ mt: 2 }} + > + {SPLIT_VALUES.map((value) => ( + } + label={t( + `common:${value === "val" ? "validation" : value}`, + )} + /> + ))} + + + + {t("explainers:label.percentageOfSplitToUse")} + + + setPercentage(v)} + valueLabelDisplay="auto" + step={1} + marks={[ + { value: 0, label: "0%" }, + { value: 25, label: "25%" }, + { value: 50, label: "50%" }, + { value: 75, label: "75%" }, + { value: 100, label: "100%" }, + ]} + min={0} + max={100} + sx={{ flex: 1 }} + /> + { + const val = + e.target.value === "" ? "" : Number(e.target.value); + if (val === "" || (val >= 0 && val <= 100)) { + setPercentage(val); + } + }} + inputProps={{ min: 0, max: 100 }} + sx={{ width: "80px" }} + /> + + setShuffle(e.target.checked)} + /> + } + label={t("explainers:label.shuffleRows")} + /> + + + + Percentage: {{ percentage }}% | Rows selected: + {{ rowsSelected: rowsSelectedByPercentage }}/{" "} + {{ totalRows: rowsInSplit }} + + + + ) : ( + + + {t("explainers:label.rowsSelectedManually", { + selected: selectedRowIndices.size, + total: totalRows, + })} + + + )} + + + + + + )} + )} - + ); } SelectDatasetStep.propTypes = { newExpl: PropTypes.shape({ - run_id: PropTypes.string, - name: PropTypes.string, + run_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), explainer_name: PropTypes.string, dataset_id: PropTypes.number, parameters: PropTypes.object, diff --git a/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx b/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx index fedf5942d..ab0c95f78 100644 --- a/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx +++ b/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Box, CircularProgress, TextField, Typography } from "@mui/material"; +import { Box, CircularProgress, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; @@ -13,16 +13,11 @@ function SetNameAndExplainerStep({ setNextEnabled, scope, taskName, - existingExplainers = [], + modelName, }) { const { enqueueSnackbar } = useSnackbar(); const [loading, setLoading] = useState(false); - const [nModifications, setNModifications] = useState(0); - const [explNameOk, setExplNameOk] = useState(false); - const [explNameError, setExplNameError] = useState(false); - const [explNameExistsError, setExplNameExistsError] = useState(false); - const [explainers, setExplainers] = useState([]); const [selectedExplainer, setSelectedExplainer] = useState({}); const [selectedExplainerOk, setSelectedExplainerOk] = useState(false); @@ -31,9 +26,26 @@ function SetNameAndExplainerStep({ const getExplainers = async () => { setLoading(true); try { - const result = await getComponentsRequest({ - selectTypes: [`${scope}Explainer`], - relatedComponent: taskName, + // Explainers related to the task are model agnostic (usable by any + // model of the task); explainers related to the run's model are + // model specific ones the model declares in COMPATIBLE_COMPONENTS. + const [taskRelated, modelRelated] = await Promise.all([ + getComponentsRequest({ + selectTypes: [`${scope}Explainer`], + relatedComponent: taskName, + }), + modelName + ? getComponentsRequest({ + selectTypes: [`${scope}Explainer`], + relatedComponent: modelName, + }) + : Promise.resolve([]), + ]); + const seen = new Set(); + const result = [...taskRelated, ...modelRelated].filter((obj) => { + if (seen.has(obj.name)) return false; + seen.add(obj.name); + return true; }); setExplainers(result.filter((obj) => !obj.name.startsWith("Fit"))); } catch (error) { @@ -52,21 +64,6 @@ function SetNameAndExplainerStep({ } }; - const handleNameInputChange = (event) => { - setNewExpl({ ...newExpl, name: event.target.value }); - setNModifications(nModifications + 1); - - if (nModifications + 1 >= 4) { - if (event.target.value.length < 4) { - setExplNameError(true); - setExplNameOk(false); - } else { - setExplNameError(false); - setExplNameOk(true); - } - } - }; - useEffect(() => { if (selectedExplainer && "name" in selectedExplainer) { setNewExpl({ @@ -82,54 +79,11 @@ function SetNameAndExplainerStep({ }, []); useEffect(() => { - if (typeof newExpl.name === "string" && newExpl.name.length >= 4) { - const normalizedName = newExpl.name.trim().toLowerCase(); - const nameExists = existingExplainers.some( - (explainer) => explainer?.name?.trim().toLowerCase() === normalizedName, - ); - - setExplNameExistsError(nameExists); - setExplNameOk(true); - setExplNameError(false); - setNModifications(4); - } else { - setExplNameOk(false); - setExplNameExistsError(false); - if (nModifications >= 4) { - setExplNameError(true); - } - } - }, [newExpl.name, nModifications, existingExplainers]); - - useEffect(() => { - if (explNameOk && selectedExplainerOk && !explNameExistsError) { - setNextEnabled(true); - } else { - setNextEnabled(false); - } - }, [explNameOk, selectedExplainerOk, explNameExistsError]); + setNextEnabled(selectedExplainerOk); + }, [selectedExplainerOk]); return ( - - {t("explainers:label.selectExplainer")} @@ -155,7 +109,6 @@ function SetNameAndExplainerStep({ SetNameAndExplainerStep.propTypes = { newExpl: PropTypes.shape({ - name: PropTypes.string, explainer_name: PropTypes.string, dataset_id: PropTypes.number, parameters: PropTypes.object, @@ -165,7 +118,7 @@ SetNameAndExplainerStep.propTypes = { setNextEnabled: PropTypes.func.isRequired, scope: PropTypes.string.isRequired, taskName: PropTypes.string, - existingExplainers: PropTypes.array, + modelName: PropTypes.string, }; export default SetNameAndExplainerStep; diff --git a/DashAI/front/src/components/explainers/explainerCache.js b/DashAI/front/src/components/explainers/explainerCache.js new file mode 100644 index 000000000..cf382b75c --- /dev/null +++ b/DashAI/front/src/components/explainers/explainerCache.js @@ -0,0 +1,31 @@ +// Stable reference returned for cache misses. +export const EMPTY_EXPLAINER_ENTRY = { + items: null, + overriddenIndexes: [], + selectedGroups: {}, +}; + +// Fallback placeholder height before a card has ever been measured. +export const DEFAULT_CARD_HEIGHT = 520; + +// Key includes the explainer type and creation time, not just the id: on +// retrain the backend reuses low ids, so a new explainer must not inherit a +// deleted one's cached plot. +export const explainerCacheKey = (scope, e) => + `${scope}-${e.id}-${e.explainer_name ?? ""}-${e.created ?? ""}`; + +// Reuse the previous object for any explainer whose meaningful fields are +// unchanged, so a poll that returns identical data keeps stable references. +// Without this, every 3s poll hands each card a brand new object, defeating +// LazyExplainerCard's React.memo and re-rendering the whole list (Plotly +// included) on every tick. +export const mergeExplainers = (prev, next) => + next.map((e) => { + const old = prev.find((p) => p.id === e.id); + return old && + old.status === e.status && + old.created === e.created && + old.explainer_name === e.explainer_name + ? old + : e; + }); diff --git a/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx b/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx index dd71cbd7d..46b20fef2 100644 --- a/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx +++ b/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx @@ -1,147 +1,19 @@ import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; -import { Box, CircularProgress, Tooltip, Typography } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { getExplorerResults } from "../../../../api/explorer"; +import { + artifactToVisualizerData, + visualizersKeys, +} from "../../../../utils/artifactVisualizerData"; import { TabularVisualizer, PlotlyJsonVisualizer, ImageVisualizer, } from "../../Visualizations"; -/** - * NullCell component to render null values in the tabular visualizer - * @param {Object} props - */ -function NullCell({}) { - const [hover, setHover] = useState(false); - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? "None" : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; - -/** - * Get the data from the orientation given. This function is used to transform the data - * from the explorer results to the format required by the tabular visualizer. - * @param {Object} data The data from the explorer results - * @param {String} orientation The orientation of the data - */ -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - // ‘dict’ (default) : dict like {column -> {index -> value}} - // Get the columns - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => { - return ( - - {params.value} - - ); - }, - }, - ...columns.map((column) => { - return { - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) { - return ; - } else if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } else if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const tooltip = params.value; - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - const tooltip = params.value; - return ( - - {params.value} - - ); - }, - }; - }), - ]; - - // Get the rows - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { - id: index, - }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; - /** * Results component to render the results of the exploration * @param {Object} props @@ -157,45 +29,15 @@ function Results({ id, updateFlag = false, setUpdateFlag = () => {} }) { const fetchExplorerResults = async () => { setLoading(true); getExplorerResults(id) - .then((results) => { - if (!results?.type) { - throw new Error("No result type specified in the response"); - } - - // Check if there is an appropriate visualizer - if (!Object.keys(visualizers).includes(results.type)) { - throw new Error(`No visualizer found for type: ${results.type}`); - } - setDataType(results.type); - - if (results.type === visualizersKeys.tabular) { - // Get the data from the orientation - const data = getDataFromOrientation( - results.data, - results.config.orient, - ); - setData({ - columns: data.columns.map((column) => { - return { - ...column, - // flex: 1, - }; - }), - rows: data.rows, - }); + .then((artifacts) => { + const [artifact] = artifacts ?? []; + if (!artifact?.type) { + throw new Error("No artifacts in the response"); } - if (results.type === visualizersKeys.plotly_json) { - setData(JSON.parse(results.data)); - } - - if (results.type === visualizersKeys.image_base64) { - setData(results.data); - } - - if (results.type === visualizersKeys.image_url) { - setData(results.data); - } + const visualizerData = artifactToVisualizerData(artifact); + setDataType(visualizerData.dataType); + setData(visualizerData.data); }) .catch((error) => { console.error(error); @@ -246,13 +88,11 @@ function Results({ id, updateFlag = false, setUpdateFlag = () => {} }) { )} - {!loading && dataType === visualizersKeys.image_base64 && ( - - )} - - {!loading && dataType === visualizersKeys.image_url && ( - - )} + {!loading && + (dataType === visualizersKeys.image_base64 || + dataType === visualizersKeys.image_url) && ( + + )} ); } diff --git a/DashAI/front/src/components/generative/CreateSessionCenter.jsx b/DashAI/front/src/components/generative/CreateSessionCenter.jsx index 2480df2b4..f43d35f2c 100644 --- a/DashAI/front/src/components/generative/CreateSessionCenter.jsx +++ b/DashAI/front/src/components/generative/CreateSessionCenter.jsx @@ -29,6 +29,7 @@ export default function CreateSessionCenter() { step, models, loadingModels, + markModelDownloaded, selectedModel, handleSelectModel, formik, @@ -67,9 +68,20 @@ export default function CreateSessionCenter() { } }, [step]); - const canGoNext = !!selectedModel; + // Read the download status from the (in place updated) models list so the + // gate reacts to an inline download without needing selectedModel to change. + const selectedModelState = + models.find((m) => m.name === selectedModel?.name) || selectedModel; + const selectedNeedsDownload = + Boolean(selectedModelState?.metadata?.requires_download) && + !selectedModelState?.downloaded; + + const canGoNext = !!selectedModel && !selectedNeedsDownload; const canCreate = - !!selectedModel && !!formik.values.name?.trim() && !submitting; + !!selectedModel && + !selectedNeedsDownload && + !!formik.values.name?.trim() && + !submitting; return ( + markModelDownloaded(model.name, isDownloaded) + } categoryKey="task_display_name" searchPlaceholder={t("generative:label.searchModels")} tourDataFor={tourContext?.run ? "model-card-qwen" : null} diff --git a/DashAI/front/src/components/generative/CreateSessionContext.jsx b/DashAI/front/src/components/generative/CreateSessionContext.jsx index 43a639088..90cfecdc6 100644 --- a/DashAI/front/src/components/generative/CreateSessionContext.jsx +++ b/DashAI/front/src/components/generative/CreateSessionContext.jsx @@ -42,14 +42,14 @@ export function CreateSessionProvider({ children }) { const [selectedModel, setSelectedModel] = useState(null); const [submitting, setSubmitting] = useState(false); - // Load all generative models grouped by their compatible task. - // Re-fetches when language changes so display_name/description are translated. - useEffect(() => { - if (!tasks || tasks.length === 0) return; - let cancelled = false; + // Load all generative models grouped by their compatible task. Exposed as + // refetchModels so callers (e.g. an inline download control) can refresh the + // list when a model's downloaded state changes. Re-fetches when language + // changes so display_name/description are translated. + const loadModels = useCallback(() => { + if (!tasks || tasks.length === 0) return Promise.resolve(); setLoadingModels(true); - - Promise.all( + return Promise.all( tasks.map((task) => getRelatedComponents(task.name).then((components) => components.map((c) => ({ @@ -61,7 +61,6 @@ export function CreateSessionProvider({ children }) { ), ) .then((perTaskLists) => { - if (cancelled) return; // Deduplicate by model name (a model may appear under several tasks). const seen = new Set(); const flat = []; @@ -79,14 +78,14 @@ export function CreateSessionProvider({ children }) { }); }) .finally(() => { - if (!cancelled) setLoadingModels(false); + setLoadingModels(false); }); - - return () => { - cancelled = true; - }; }, [tasks, enqueueSnackbar, t]); + useEffect(() => { + loadModels(); + }, [loadModels]); + const processedProperties = useMemo( () => selectedModel?.schema?.properties @@ -193,14 +192,36 @@ export function CreateSessionProvider({ children }) { [existingSessions], ); + // A model whose download was removed can no longer be used to create a + // session, so it must not stay selected. + const isUnavailable = (model) => + Boolean(model?.metadata?.requires_download) && !model?.downloaded; + // Sync selectedModel from URL param on load and after language-triggered // model refetch so display_name / description reflect the active language. + // If the URL points at a model that is no longer downloaded, drop back to + // the model selection step. useEffect(() => { if (!modelName || models.length === 0) return; const match = models.find((m) => m.name === modelName); - if (match) handleSelectModel(match); + if (!match) return; + if (isUnavailable(match)) { + setSelectedModel(null); + navigate("/app/generative/sessions/new"); + } else { + handleSelectModel(match); + } }, [modelName, models]); + // An undownloaded model may stay selected so its description is visible and + // it can be downloaded inline; the Next button gates on the download status. + // Only drop the selection if the model disappears from the list entirely. + useEffect(() => { + if (!selectedModel) return; + const match = models.find((m) => m.name === selectedModel.name); + if (!match) setSelectedModel(null); + }, [models]); + const handleNext = () => { if (step === 0 && selectedModel) navigate(`/app/generative/sessions/new/${selectedModel.name}`); @@ -215,10 +236,23 @@ export function CreateSessionProvider({ children }) { formik.submitForm(); }; + // Flip a single model's downloaded flag in place. Used when an inline + // download/delete finishes so the list updates without a full refetch + // (which would swap in the loading spinner and reset the scroll position). + const markModelDownloaded = useCallback((name, isDownloaded) => { + setModels((prev) => + prev.map((m) => + m.name === name ? { ...m, downloaded: isDownloaded } : m, + ), + ); + }, []); + const value = { step, models, loadingModels, + refetchModels: loadModels, + markModelDownloaded, selectedModel, handleSelectModel, formik, diff --git a/DashAI/front/src/components/generative/GenerativeChat.jsx b/DashAI/front/src/components/generative/GenerativeChat.jsx index 5f12cd2d3..6fa754c9d 100644 --- a/DashAI/front/src/components/generative/GenerativeChat.jsx +++ b/DashAI/front/src/components/generative/GenerativeChat.jsx @@ -14,7 +14,16 @@ import { postProcess } from "../../api/process"; import { enqueueGenerativeProcessJob } from "../../api/job"; import { startJobQueue } from "../../api/job"; import { getHistoryBySessionId, getSessionById } from "../../api/session"; +import { + getComponentById, + getComponentDownloadStatus, +} from "../../api/component"; +import { getRelatedComponents } from "../../api/generativeTask"; import InfoSessionModal from "./InfoSessionModal"; +import ModelSwitcher from "./ModelSwitcher"; +import ComponentDownloadControl, { + useComponentDownloadState, +} from "../models/model/ComponentDownloadControl"; import { useSnackbar } from "notistack"; import { MediaInput } from "./MediaInput"; import { Trans, useTranslation } from "react-i18next"; @@ -29,6 +38,8 @@ export default function GenerativeChat() { selectedTaskName: taskName, tasks, paramsVersion, + setParamsVersion, + fetchSessions, } = useGenerative(); const inputsCardinality = useMemo(() => { @@ -45,6 +56,8 @@ export default function GenerativeChat() { const [showScrollButton, setShowScrollButton] = useState(false); const [sessionInfo, setSessionInfo] = useState(null); const [sessionInfoVisible, setSessionInfoVisible] = useState(false); + const [modelComponent, setModelComponent] = useState(null); + const [modelsByName, setModelsByName] = useState({}); const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(["generative"]); const tourContext = useTourContext(); @@ -77,6 +90,54 @@ export default function GenerativeChat() { }); }; + // Resolve the session model's metadata plus a reconciled download status, so + // the chat can block input and offer a download when the weights are missing + // (e.g. after switching to a not downloaded model or deleting its download). + const modelName = sessionInfo?.model_name; + const refreshModelStatus = () => { + if (!modelName) { + setModelComponent(null); + return; + } + Promise.all([ + getComponentById(modelName), + getComponentDownloadStatus(modelName), + ]) + .then(([component, status]) => { + setModelComponent({ ...component, downloaded: status.downloaded }); + }) + .catch(() => setModelComponent(null)); + }; + + useEffect(() => { + refreshModelStatus(); + }, [modelName, paramsVersion]); + + // Map component name -> display name for the task's models, used to render + // model change history events with friendly names instead of class names. + useEffect(() => { + const currentTaskName = sessionInfo?.task_name; + if (!currentTaskName) return; + getRelatedComponents(currentTaskName) + .then((components) => { + const map = {}; + (components || []).forEach((c) => { + map[c.name] = c.display_name || c.name; + }); + setModelsByName(map); + }) + .catch(() => setModelsByName({})); + }, [sessionInfo?.task_name]); + + // Use the live download state so an in-progress download keeps the input + // blocked even when the backend already reports the (partial) files as + // present, and unblocks the moment the download actually finishes. + const { downloaded: liveDownloaded, downloading: liveDownloading } = + useComponentDownloadState(modelComponent || { name: modelName || "" }); + const modelBlocked = + Boolean(modelComponent?.metadata?.requires_download) && + !(liveDownloaded && !liveDownloading); + const getMessages = () => { getProcessesBySessionId(sessionId).then((response) => { setIsLoadingMessage(false); @@ -199,23 +260,37 @@ export default function GenerativeChat() { }); let historyObject = history.map((entry) => { + const isModelChange = entry.changes.some((c) => c.parameter === "model"); return { type: "history", timestamp: entry.timestamp, id: entry.id, - changedMessage: entry.changes.map((change) => ( - - {change.parameter}: {change.oldValue}{" "} - {change.newValue}{" "} - - )), + isModelChange, + changedMessage: entry.changes.map((change) => { + const isModel = change.parameter === "model"; + const label = isModel + ? t("generative:label.sessionModel") + : change.parameter; + const oldValue = isModel + ? modelsByName[change.oldValue] || change.oldValue + : change.oldValue; + const newValue = isModel + ? modelsByName[change.newValue] || change.newValue + : change.newValue; + return ( + + {label}: {oldValue} {" "} + {newValue}{" "} + + ); + }), }; }); @@ -254,17 +329,17 @@ export default function GenerativeChat() { {sessionInfo?.description ? ":" : null} {sessionInfo?.description} - - setSessionInfoVisible(true)}> - - + + { + getSessionInfo(); + fetchSessions(); + setParamsVersion((v) => v + 1); + }} + /> @@ -310,8 +385,17 @@ export default function GenerativeChat() { > {message.type === "history" ? ( - - Parameters updated: {message.changedMessage} + + {message.isModelChange + ? "Model changed: " + : "Parameters updated: "} + {message.changedMessage} ) : ( @@ -361,15 +445,40 @@ export default function GenerativeChat() { )} - {/* Chat input */} - { - handleSendMessage(input); - }} - isLoading={isLoadingMessage} - inputsCardinality={inputsCardinality} - /> + {/* Chat input, or a download prompt when the model is not available */} + {modelBlocked ? ( + + + {t("generative:label.modelNotDownloaded")} + + refreshModelStatus()} + /> + + ) : ( + { + handleSendMessage(input); + }} + isLoading={isLoadingMessage} + inputsCardinality={inputsCardinality} + /> + )} {/* Session Info Modal */} {sessionInfo && ( diff --git a/DashAI/front/src/components/generative/InfoSessionModal.jsx b/DashAI/front/src/components/generative/InfoSessionModal.jsx index c8b9b7451..5d82c8d12 100644 --- a/DashAI/front/src/components/generative/InfoSessionModal.jsx +++ b/DashAI/front/src/components/generative/InfoSessionModal.jsx @@ -1,10 +1,7 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import IconButton from "@mui/material/IconButton"; -import Modal from "@mui/material/Modal"; import Paper from "@mui/material/Paper"; import Chip from "@mui/material/Chip"; -import CloseIcon from "@mui/icons-material/Close"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; @@ -13,151 +10,78 @@ import TableRow from "@mui/material/TableRow"; import { formatDate } from "../../utils"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material/styles"; +import InfoModal from "../shared/InfoModal"; export default function InfoSessionModal({ sessionData, open, onClose }) { - // If no session data is provided, don't render anything - if (!sessionData) return null; - const { t } = useTranslation(["generative", "common"]); const theme = useTheme(); - return ( - - - {/* Modal Header */} - - - - {t("generative:label.sessionInformation")} - - - {sessionData.name} - - - - - - + // If no session data is provided, don't render anything + if (!sessionData) return null; - {/* Modal Content */} - - - - - {t("common:model")}:{" "} - - {sessionData.model_name} - - - + const rows = [ + { label: t("common:id"), value: sessionData.id }, + { label: t("common:created"), value: formatDate(sessionData.created) }, + { + label: t("common:lastModified"), + value: formatDate(sessionData.last_modified), + }, + ]; - - {t("common:parameters")}:{" "} - - - - - {Object.entries(sessionData.parameters || {}).map( - ([key, value]) => ( - - - {key.replace(/_/g, " ")} - - {value} - - ), - )} - -
-
+ const extraContent = ( + <> + + + + {t("common:model")}:{" "} + + {sessionData.model_name} + + + - - {t("common:metadata")}: - - - - - - - {t("common:id")} - - {sessionData.id} - - - - {t("common:created")} - - - {formatDate(sessionData.created)} - - - + + {t("common:parameters")}:{" "} + + +
+ + {Object.entries(sessionData.parameters || {}).map( + ([key, value]) => ( + - {t("common:lastModified")} - - - {formatDate(sessionData.last_modified)} + {key.replace(/_/g, " ")} + {value} - -
-
-
-
-
+ ), + )} + + + + + ); + + return ( + ); } diff --git a/DashAI/front/src/components/generative/ModelSwitcher.jsx b/DashAI/front/src/components/generative/ModelSwitcher.jsx new file mode 100644 index 000000000..7a517b2d0 --- /dev/null +++ b/DashAI/front/src/components/generative/ModelSwitcher.jsx @@ -0,0 +1,119 @@ +import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { Box, MenuItem, Select, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import { getRelatedComponents } from "../../api/generativeTask"; +import { updateGenerativeSession } from "../../api/session"; +import { + getComponentDownloadState, + subscribeAnyDownloadState, +} from "../models/model/ComponentDownloadControl"; + +/** + * Session-level model switcher: lets the user change the model used by a + * generative session, restricted to models of the same task. Models that are + * download-required but not yet downloaded are shown disabled. + */ +export default function ModelSwitcher({ + sessionId, + taskName, + currentModelName, + onChanged, +}) { + const { t } = useTranslation(["generative", "common"]); + const { enqueueSnackbar } = useSnackbar(); + const [models, setModels] = useState([]); + const [saving, setSaving] = useState(false); + // Bump to re-render when any download state changes so the labels reflect + // downloads that finished after the model list was fetched. + const [, setDownloadVersion] = useState(0); + + useEffect(() => { + if (!taskName) return; + getRelatedComponents(taskName) + .then((components) => setModels(components || [])) + .catch(() => setModels([])); + }, [taskName]); + + useEffect( + () => subscribeAnyDownloadState(() => setDownloadVersion((v) => v + 1)), + [], + ); + + const handleChange = async (event) => { + const newModel = event.target.value; + if (!newModel || newModel === currentModelName) return; + setSaving(true); + try { + await updateGenerativeSession({ + id: sessionId, + formData: { model_name: newModel }, + }); + if (onChanged) onChanged(newModel); + } catch (error) { + const status = error?.response?.status; + enqueueSnackbar( + status === 409 + ? t("common:componentDownload.mustDownload") + : t("generative:error.modelSwitchFailed"), + { variant: "error" }, + ); + } finally { + setSaving(false); + } + }; + + // Ensure the current model is always a selectable value even if the list + // has not loaded yet (avoids an out-of-range MUI Select warning). + const hasCurrent = models.some((m) => m.name === currentModelName); + const options = hasCurrent + ? models + : [{ name: currentModelName, display_name: currentModelName }, ...models]; + + if (!currentModelName) return null; + + return ( + + + {t("generative:label.sessionModel")} + + + + ); +} + +ModelSwitcher.propTypes = { + sessionId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + taskName: PropTypes.string, + currentModelName: PropTypes.string, + onChanged: PropTypes.func, +}; diff --git a/DashAI/front/src/components/generative/ParamsBar.jsx b/DashAI/front/src/components/generative/ParamsBar.jsx index a6aa35ca7..3b85c76dd 100644 --- a/DashAI/front/src/components/generative/ParamsBar.jsx +++ b/DashAI/front/src/components/generative/ParamsBar.jsx @@ -21,6 +21,7 @@ export default function ParamsBar({ onToggle }) { const { selectedSessionId, selectedTaskName: taskName, + paramsVersion, setParamsVersion, } = useGenerative(); const [parameters, setParameters] = useState({}); @@ -60,7 +61,7 @@ export default function ParamsBar({ onToggle }) { } }); }); - }, [selectedSessionId, t]); + }, [selectedSessionId, t, paramsVersion]); useEffect(() => { if (selectedModel?.schema?.properties) { diff --git a/DashAI/front/src/components/generative/SessionBar.jsx b/DashAI/front/src/components/generative/SessionBar.jsx index b8ad8a2c5..7c089af96 100644 --- a/DashAI/front/src/components/generative/SessionBar.jsx +++ b/DashAI/front/src/components/generative/SessionBar.jsx @@ -47,16 +47,16 @@ export default function SessionBar({ onToggle }) { ), ), ]; - const initialOpenState = {}; - uniqueDisplayNames.forEach((displayName) => { - initialOpenState[displayName] = false; - }); setOpenSections((prev) => { - // Only update if display names have changed const prevKeys = Object.keys(prev).sort().join(","); - const newKeys = Object.keys(initialOpenState).sort().join(","); + const newKeys = uniqueDisplayNames.slice().sort().join(","); if (prevKeys === newKeys) return prev; - return initialOpenState; + // Preserve existing open/close state; initialize new keys as closed + const merged = {}; + uniqueDisplayNames.forEach((displayName) => { + merged[displayName] = displayName in prev ? prev[displayName] : false; + }); + return merged; }); }, [sessions, tasks]); diff --git a/DashAI/front/src/components/jobs/JobDetailsDialog.jsx b/DashAI/front/src/components/jobs/JobDetailsDialog.jsx index 747af922a..bcecc0588 100644 --- a/DashAI/front/src/components/jobs/JobDetailsDialog.jsx +++ b/DashAI/front/src/components/jobs/JobDetailsDialog.jsx @@ -12,6 +12,7 @@ import { Box, Divider, CircularProgress, + LinearProgress, } from "@mui/material"; import { useTranslation } from "react-i18next"; import { getJobDetails } from "../../api/job"; @@ -189,6 +190,44 @@ const JobDetailsDialog = ({ job, open, onClose }) => {
+ {displayJob.status === "started" && ( + + + {t("common:jobQueue.details.progress")} + + {displayJob.progress_message && ( + + {displayJob.progress_message} + + )} + + + + + {displayJob.progress != null && ( + + {Math.round(displayJob.progress)}% + + )} + + + )} + {displayJob.error_msg && ( diff --git a/DashAI/front/src/components/jobs/JobQueueWidget.jsx b/DashAI/front/src/components/jobs/JobQueueWidget.jsx index caf892e30..ab9c03f2e 100644 --- a/DashAI/front/src/components/jobs/JobQueueWidget.jsx +++ b/DashAI/front/src/components/jobs/JobQueueWidget.jsx @@ -12,6 +12,7 @@ import { Badge, Collapse, List, + LinearProgress, CircularProgress, ListItem, ListItemButton, @@ -517,6 +518,7 @@ const JobQueueWidget = () => { @@ -531,26 +533,54 @@ const JobQueueWidget = () => { } secondary={ - - {getStatusText(job.status, t)} - {job.status === "error" && ( - + + + {job.status === "started" && + job.progress_message + ? job.progress_message + : getStatusText(job.status, t)} + + {job.status === "error" && ( + + + + )} + + {job.status === "started" && ( + - - + sx={{ + mt: 0.5, + height: 4, + borderRadius: 1, + }} + /> )} - +
} /> diff --git a/DashAI/front/src/components/jobs/JobQueueWidget.test.jsx b/DashAI/front/src/components/jobs/JobQueueWidget.test.jsx index ea66f8d53..e8b32c303 100644 --- a/DashAI/front/src/components/jobs/JobQueueWidget.test.jsx +++ b/DashAI/front/src/components/jobs/JobQueueWidget.test.jsx @@ -2,6 +2,9 @@ import React from "react"; import { screen } from "@testing-library/react"; import { renderWithProviders } from "../../test-utils/renderWithProviders"; +// Mutable job list the mocked useJobManager returns; set per test. +let mockJobs = []; + // Mock API modules before importing component jest.mock("../../api/job", () => ({ deleteJob: jest.fn(), @@ -11,7 +14,7 @@ jest.mock("../../api/job", () => ({ jest.mock("../../hooks/useJobPolling", () => ({ useJobManager: () => ({ - jobs: [], + jobs: mockJobs, loading: false, error: null, refresh: jest.fn(), @@ -20,6 +23,12 @@ jest.mock("../../hooks/useJobPolling", () => ({ import JobQueueWidget from "./JobQueueWidget"; +beforeEach(() => { + mockJobs = []; + // Ensure the job list is expanded so rows (and their bars) render. + localStorage.setItem("jobQueueWidgetExpanded", "true"); +}); + describe("JobQueueWidget", () => { it("renders without crashing", () => { renderWithProviders(); @@ -29,4 +38,41 @@ describe("JobQueueWidget", () => { renderWithProviders(); expect(screen.getByText("Job Queue")).toBeInTheDocument(); }); + + it("shows a determinate bar and phase message for a running job", () => { + mockJobs = [ + { + id: "job-1", + task_type: "ModelJob", + job_name: "Train model", + status: "started", + last_update: "2026-07-01 00:00:00.000", + progress: 42, + progress_message: "Training", + }, + ]; + renderWithProviders(); + + expect(screen.getByText("Training")).toBeInTheDocument(); + const bar = screen.getByRole("progressbar"); + expect(bar).toHaveAttribute("aria-valuenow", "42"); + }); + + it("shows an indeterminate bar when a running job has no progress", () => { + mockJobs = [ + { + id: "job-2", + task_type: "ModelJob", + job_name: "Train model", + status: "started", + last_update: "2026-07-01 00:00:00.000", + progress: null, + progress_message: null, + }, + ]; + renderWithProviders(); + + const bar = screen.getByRole("progressbar"); + expect(bar).not.toHaveAttribute("aria-valuenow"); + }); }); diff --git a/DashAI/front/src/components/models/AddModelDialog.jsx b/DashAI/front/src/components/models/AddModelDialog.jsx index 61cde7eb3..a2d36365f 100644 --- a/DashAI/front/src/components/models/AddModelDialog.jsx +++ b/DashAI/front/src/components/models/AddModelDialog.jsx @@ -12,6 +12,7 @@ import { TextField, Box, IconButton, + Tooltip, Typography, } from "@mui/material"; import { Close as CloseIcon } from "@mui/icons-material"; @@ -23,6 +24,8 @@ import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; import useSchema from "../../hooks/useSchema"; import { generateSequentialName } from "../../utils/nameGenerator"; import { createRun } from "../../api/run"; +import { getRequiredDownloads } from "../../api/component"; +import { subscribeAnyDownloadState } from "./model/ComponentDownloadControl"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { checkIfHaveOptimazers } from "../../utils/schema"; @@ -37,6 +40,7 @@ function AddModelDialog({ onClose, session, preselectedModel, + preselectedModelObject, existingRuns = [], onRunCreated, }) { @@ -51,16 +55,22 @@ function AddModelDialog({ const [hasUserTouchedName, setHasUserTouchedName] = useState(false); const [goalMetric, setGoalMetric] = useState(""); const [hasLoadedInitialParams, setHasLoadedInitialParams] = useState(false); + const [modelDownloaded, setModelDownloaded] = useState(true); + const [missingNested, setMissingNested] = useState([]); const { t } = useTranslation(["models", "common"]); const { defaultValues: defaultModelParams } = useSchema({ modelName: open ? selectedModel : null, }); + // Fetched as soon as the dialog opens (not gated on activeStep === 1) so it + // has the whole step-1 dwell time to resolve before the user reaches step 2 + // — otherwise the "Parámetros del Optimizador" section briefly renders + // empty right after clicking "Siguiente", shrinking the dialog for a beat. const { defaultValues: defaultOptimizerParams, loading: optimizerSchemaLoading, } = useSchema({ - modelName: open && activeStep === 1 ? selectedOptimizer : null, + modelName: open ? selectedOptimizer : null, }); const tourContext = useTourContext(); @@ -96,6 +106,44 @@ function AddModelDialog({ } }, [preselectedModel, selectedModel]); + useEffect(() => { + const comp = preselectedModelObject; + const requiresDownload = Boolean(comp?.metadata?.requires_download); + const isDownloaded = Boolean(comp?.downloaded); + setModelDownloaded(!requiresDownload || isDownloaded); + }, [preselectedModelObject]); + + // Block advancing while any component selected inside the model parameters + // still needs downloading. The check walks the nested parameters server-side + // and re-runs after an inline download/delete finishes anywhere. + useEffect(() => { + if ( + !open || + activeStep !== 0 || + !modelParameters || + Object.keys(modelParameters).length === 0 + ) { + setMissingNested([]); + return; + } + let cancelled = false; + const check = async () => { + try { + const missing = await getRequiredDownloads(modelParameters); + if (!cancelled) setMissingNested(missing); + } catch { + if (!cancelled) setMissingNested([]); + } + }; + const timer = setTimeout(check, 300); + const unsubscribe = subscribeAnyDownloadState(() => check()); + return () => { + cancelled = true; + clearTimeout(timer); + unsubscribe(); + }; + }, [open, activeStep, JSON.stringify(modelParameters)]); + useEffect(() => { if ( selectedModel && @@ -332,6 +380,7 @@ function AddModelDialog({ metricName={goalMetric} handleSelectedMetric={setGoalMetric} required + autoSelectDefault /> @@ -371,20 +420,44 @@ function AddModelDialog({ {t("common:back")} )} - + + + + ); @@ -399,6 +472,11 @@ AddModelDialog.propTypes = { task_name: PropTypes.string, }), preselectedModel: PropTypes.string, + preselectedModelObject: PropTypes.shape({ + name: PropTypes.string, + downloaded: PropTypes.bool, + metadata: PropTypes.object, + }), existingRuns: PropTypes.array, onRunCreated: PropTypes.func, }; diff --git a/DashAI/front/src/components/models/DatasetPredictionPanel.jsx b/DashAI/front/src/components/models/DatasetPredictionPanel.jsx index 354a9ff7e..af7163e2f 100644 --- a/DashAI/front/src/components/models/DatasetPredictionPanel.jsx +++ b/DashAI/front/src/components/models/DatasetPredictionPanel.jsx @@ -10,7 +10,7 @@ import { getPredictions, } from "../../api/predict"; import { enqueuePredictionJob } from "../../api/job"; -import { getDatasetInfo } from "../../api/datasets"; +import { getDatasets } from "../../api/datasets"; import { getModelSessionById } from "../../api/modelSession"; import { startJobPolling } from "../../utils/jobPoller"; @@ -51,19 +51,19 @@ export default function DatasetPredictionPanel({ if (!run?.id) return; setLoading(true); try { - const [availableDatasets, sessionData] = await Promise.all([ + // Only datasets compatible with the run's model (matching input + // columns) can be used. A single endpoint validates them all + // server-side and returns just the valid ids, which we use to filter + // the already-cheap full dataset list instead of fetching per-dataset + // info for every candidate up front. + const [allDatasets, validIds, sessionData] = await Promise.all([ + getDatasets(), filterDatasets({ run_id: run.id }), getModelSessionById(run.model_session_id || session?.id), ]); - const availableDatasetsWithInfo = await Promise.all( - availableDatasets.map(async (dataset) => { - const datasetInfo = await getDatasetInfo(dataset.id); - return { ...dataset, ...datasetInfo }; - }), - ); - - setDatasets(availableDatasetsWithInfo); + const validIdSet = new Set(validIds.map(String)); + setDatasets(allDatasets.filter((ds) => validIdSet.has(String(ds.id)))); setModelSession(sessionData); } catch (error) { console.error("Error loading dataset prediction data:", error); diff --git a/DashAI/front/src/components/models/HyperparameterPlots.jsx b/DashAI/front/src/components/models/HyperparameterPlots.jsx index ef5bd14de..8afce5274 100644 --- a/DashAI/front/src/components/models/HyperparameterPlots.jsx +++ b/DashAI/front/src/components/models/HyperparameterPlots.jsx @@ -1,42 +1,21 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; -import Plot from "react-plotly.js"; -import { Grid, CircularProgress, Box, Typography } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; +import { CircularProgress, Box, Typography } from "@mui/material"; import { getHyperparameterPlot as getHyperparameterPlotRequest } from "../../api/run"; import { enqueueSnackbar } from "notistack"; import { checkHowManyOptimazers } from "../../utils/schema"; -import { applyThemeToLayout } from "../../utils/plotlyTheme"; +import ArtifactViewer from "../shared/ArtifactViewer"; function HyperparameterPlots({ run }) { - const theme = useTheme(); + // Each plot now arrives as a typed artifact ({type, payload, title}) built + // server side, same contract Explainers/Explorers use - no client side + // parsing or title guessing needed. const [historicalPlot, setHistoricalPlot] = useState(null); const [slicePlot, setSlicePlot] = useState(null); const [contourPlot, setContourPlot] = useState(null); const [importancePlot, setImportancePlot] = useState(null); const [loading, setLoading] = useState(true); - const themedHistoricalLayout = useMemo( - () => applyThemeToLayout(historicalPlot?.layout, theme), - [historicalPlot, theme], - ); - const themedSliceLayout = useMemo( - () => applyThemeToLayout(slicePlot?.layout, theme), - [slicePlot, theme], - ); - const themedContourLayout = useMemo( - () => applyThemeToLayout(contourPlot?.layout, theme), - [contourPlot, theme], - ); - const themedImportanceLayout = useMemo( - () => applyThemeToLayout(importancePlot?.layout, theme), - [importancePlot, theme], - ); - - const parsePlot = (plot) => { - return JSON.parse(plot); - }; - const optimizables = checkHowManyOptimazers({ params: run.parameters, }); @@ -52,18 +31,18 @@ function HyperparameterPlots({ run }) { getHyperparameterPlotRequest(run.id, 4), ]); - setHistoricalPlot(parsePlot(historical)); - setSlicePlot(parsePlot(slice)); - setContourPlot(parsePlot(contour)); - setImportancePlot(parsePlot(importance)); + setHistoricalPlot(historical); + setSlicePlot(slice); + setContourPlot(contour); + setImportancePlot(importance); } else if (optimizables === 1) { const [historical, slice] = await Promise.all([ getHyperparameterPlotRequest(run.id, 1), getHyperparameterPlotRequest(run.id, 2), ]); - setHistoricalPlot(parsePlot(historical)); - setSlicePlot(parsePlot(slice)); + setHistoricalPlot(historical); + setSlicePlot(slice); } } catch (error) { enqueueSnackbar("Error while trying to obtain hyperparameter plots", { @@ -167,65 +146,36 @@ function HyperparameterPlots({ run }) { ); } + const artifacts = [ + historicalPlot, + slicePlot, + optimizables >= 2 && contourPlot, + optimizables >= 2 && importancePlot, + ].filter(Boolean); + return ( - - {historicalPlot && ( - - - - )} - - {slicePlot && ( - - - - )} - - {optimizables >= 2 && contourPlot && ( - - - - )} - - {optimizables >= 2 && importancePlot && ( - - - - )} - + + {artifacts.map((artifact, index) => ( + + ))} + ); } diff --git a/DashAI/front/src/components/models/InfoSessionModal.jsx b/DashAI/front/src/components/models/InfoSessionModal.jsx index ecf362494..01e0426b5 100644 --- a/DashAI/front/src/components/models/InfoSessionModal.jsx +++ b/DashAI/front/src/components/models/InfoSessionModal.jsx @@ -1,16 +1,6 @@ -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import IconButton from "@mui/material/IconButton"; -import Modal from "@mui/material/Modal"; -import Paper from "@mui/material/Paper"; -import CloseIcon from "@mui/icons-material/Close"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableRow from "@mui/material/TableRow"; -import { formatDate } from "../../utils"; import { useTranslation } from "react-i18next"; +import InfoModal from "../shared/InfoModal"; +import SessionInfoContent from "./SessionInfoContent"; export default function InfoSessionModal({ sessionData, @@ -21,157 +11,23 @@ export default function InfoSessionModal({ }) { const { t } = useTranslation(["common"]); - // Find the associated dataset name - const getDatasetName = () => { - if (!sessionData.dataset_id || !datasets.length) { - return t("common:unknown"); - } - const dataset = datasets.find((d) => d.id === sessionData.dataset_id); - return dataset ? dataset.name : t("common:datasetNotFound"); - }; - - // Get task display name - const getTaskDisplayName = () => { - if (!sessionData.task_name) return t("common:unknown"); - const task = tasks.find((t) => t.name === sessionData.task_name); - return ( - task?.metadata?.display_name || - sessionData.task_name - .replace("Task", "") - .replace(/([A-Z])/g, " $1") - .trim() - ); - }; - // If no session data is provided, don't render anything if (!sessionData) return null; return ( - + } open={open} onClose={onClose} - aria-labelledby="session-info-modal" - aria-describedby="session-information-details" - > - - {/* Modal Header */} - - - - {t("common:sessionInformation")} - - - {sessionData.name} - - - - - - - - - {/* Description Section - Only show if description exists */} - {sessionData.description && sessionData.description.trim() && ( - - - {t("common:description")} - - - {sessionData.description} - - - )} - - - {t("common:metadata")} - - - - - - - {t("common:id")} - - {sessionData.id} - - - - {t("common:task")} - - {getTaskDisplayName()} - - - - {t("common:associatedDataset")} - - {getDatasetName()} - - - - {t("common:createdAt")} - - - {formatDate(sessionData.created)} - - - - - {t("common:lastModified")} - - - {formatDate(sessionData.last_modified)} - - - -
-
-
-
-
+ /> ); } diff --git a/DashAI/front/src/components/models/LiveMetricsChart.jsx b/DashAI/front/src/components/models/LiveMetricsChart.jsx index 6e91b53de..dfe081426 100644 --- a/DashAI/front/src/components/models/LiveMetricsChart.jsx +++ b/DashAI/front/src/components/models/LiveMetricsChart.jsx @@ -1,30 +1,61 @@ import { Box, - FormControl, - InputLabel, - MenuItem, - Select, - Tabs, - Tab, + ToggleButton, + Tooltip, Typography, Button, ButtonGroup, } from "@mui/material"; -import { - LineChart, - Line, - XAxis, - YAxis, - Tooltip, - Legend, - ResponsiveContainer, -} from "recharts"; +import { useTheme, alpha } from "@mui/material/styles"; +import Plot from "react-plotly.js"; import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { getModelSessionById } from "../../api/modelSession"; +import ResultsGraphsParameters from "../../pages/results/components/ResultsGraphsParameters"; +import PillToggleButtonGroup from "../shared/PillToggleButtonGroup"; +import PlotActions from "../shared/PlotActions"; +import { getTraceColors } from "../../utils/chartColors"; + +function toFinalValue(value) { + const resolved = Array.isArray(value) + ? (value[value.length - 1]?.value ?? null) + : value; + const num = Number(resolved); + return Number.isNaN(num) ? null : num; +} + +// Non-iterative models (e.g. plain scikit-learn ones) never call +// calculate_metrics during training, so the only row the backend ever writes +// for TRAIN/VALIDATION is a single final "LAST" value - the same story TEST +// always has, since it's only ever computed once at the end regardless of +// model. The live-metrics websocket only understands TRIAL/STEP/EPOCH levels, +// so without this fallback that single final value has nowhere to render and +// the tab just shows "no metrics available" even though the value exists. +// Used only for splits with zero real TRIAL/STEP/EPOCH data (see +// `hasAnyRealMetrics` below), so it never overwrites/mixes with a real curve. +function toFallbackBuckets(rawMetrics) { + if (!rawMetrics) return null; + const formatted = {}; + for (const metricName in rawMetrics) { + const value = rawMetrics[metricName]; + formatted[metricName] = Array.isArray(value) + ? value + : [{ step: 1, value, timestamp: new Date().toISOString() }]; + } + if (Object.keys(formatted).length === 0) return null; + return { TRIAL: formatted, STEP: formatted, EPOCH: formatted }; +} + +function hasAnyRealMetrics(splitData) { + if (!splitData) return false; + return ["TRIAL", "STEP", "EPOCH"].some( + (lvl) => splitData[lvl] && Object.keys(splitData[lvl]).length > 0, + ); +} export function LiveMetricsChart({ run }) { const { t } = useTranslation("models"); + const theme = useTheme(); const [level, setLevel] = useState(null); const [split, setSplit] = useState("TRAIN"); const [data, setData] = useState({}); @@ -42,33 +73,6 @@ export function LiveMetricsChart({ run }) { }); const socketRef = useRef(null); - useEffect(() => { - if (run.status === 3 && run.test_metrics) { - setData((prev) => { - const next = structuredClone(prev); - - const formattedTestMetrics = {}; - for (const metricName in run.test_metrics) { - const value = run.test_metrics[metricName]; - if (Array.isArray(value)) { - formattedTestMetrics[metricName] = value; - } else { - formattedTestMetrics[metricName] = [ - { step: 1, value: value, timestamp: new Date().toISOString() }, - ]; - } - } - - next.TEST = { - TRIAL: formattedTestMetrics, - STEP: formattedTestMetrics, - EPOCH: formattedTestMetrics, - }; - return next; - }); - } - }, [run.status, run.test_metrics]); - useEffect(() => { if (socketRef.current) { socketRef.current.close(); @@ -133,38 +137,6 @@ export function LiveMetricsChart({ run }) { }); }; - ws.onclose = () => { - if (run.test_metrics) { - setData((prev) => { - // Skip if TEST data already exists to avoid duplicate re-render - if (prev.TEST && Object.keys(prev.TEST).length > 0) { - return prev; - } - - const next = structuredClone(prev); - - const formattedTestMetrics = {}; - for (const metricName in run.test_metrics) { - const value = run.test_metrics[metricName]; - if (Array.isArray(value)) { - formattedTestMetrics[metricName] = value; - } else { - formattedTestMetrics[metricName] = [ - { step: 1, value: value, timestamp: new Date().toISOString() }, - ]; - } - } - - next.TEST = { - TRIAL: formattedTestMetrics, - STEP: formattedTestMetrics, - EPOCH: formattedTestMetrics, - }; - return next; - }); - } - }; - ws.onerror = (error) => { console.error("WebSocket error:", error); }; @@ -200,50 +172,76 @@ export function LiveMetricsChart({ run }) { }; }, [run.model_session_id]); + // Fallback bucket per split, built from the run's final metrics rather + // than the websocket. Only ever read when the split has zero real + // TRIAL/STEP/EPOCH data (see `splitHasRealData` below), so it never + // overwrites or mixes with an actual curve. + const fallbackBySplit = useMemo( + () => ({ + TRAIN: toFallbackBuckets(run.train_metrics), + VALIDATION: toFallbackBuckets(run.validation_metrics), + TEST: toFallbackBuckets(run.test_metrics), + }), + [run.train_metrics, run.validation_metrics, run.test_metrics], + ); + + const splitHasRealData = hasAnyRealMetrics(data[split]); + const splitFallback = fallbackBySplit[split]; + const filteredMetrics = useMemo(() => { - const metrics = data[split]?.[level] ?? {}; + const metrics = splitHasRealData + ? (data[split]?.[level] ?? {}) + : (splitFallback?.[level] ?? {}); const allowedMetrics = availableMetrics[split] ?? []; return Object.fromEntries( Object.entries(metrics).filter(([name]) => allowedMetrics.includes(name)), ); - }, [data, split, level, availableMetrics]); - - const chartData = useMemo(() => { - if (Object.keys(filteredMetrics).length === 0) return []; - - const allSteps = new Set(); - for (const metricName in filteredMetrics) { - const metricData = filteredMetrics[metricName]; - if (Array.isArray(metricData)) { - metricData.forEach((point) => { - allSteps.add(point.step); - }); - } - } - - const sortedSteps = Array.from(allSteps).sort((a, b) => a - b); - - return sortedSteps.map((step) => { - const point = { x: step }; - for (const metricName in filteredMetrics) { - const metricData = filteredMetrics[metricName]; - if (Array.isArray(metricData)) { - const dataPoint = metricData.find((p) => p.step === step); - point[metricName] = dataPoint?.value ?? null; - } else { - point[metricName] = null; - } - } - return point; + }, [data, split, level, availableMetrics, splitHasRealData, splitFallback]); + + // Compact final-value summary for whichever split is selected — same + // numbers shown in the session's comparison table, scoped to the split + // currently picked here instead of a fixed one. + const summaryMetrics = useMemo(() => { + const rawMetrics = run[`${split.toLowerCase()}_metrics`] ?? {}; + return Object.entries(rawMetrics) + .map(([name, value]) => [name, toFinalValue(value)]) + .filter(([, value]) => value !== null); + }, [run.train_metrics, run.validation_metrics, run.test_metrics, split]); + + // One small panel per metric — each keeps its own x/y scale instead of + // sharing a single overlaid axis, same "small multiples" approach used for + // the session results charts. + const panels = useMemo(() => { + const colors = getTraceColors(theme); + return selectedMetrics.map((metricName, idx) => { + const points = (filteredMetrics[metricName] ?? []) + .slice() + .sort((a, b) => a.step - b.step); + return { + metric: metricName, + x: points.map((p) => p.step), + y: points.map((p) => p.value), + color: colors[idx % colors.length], + }; }); - }, [filteredMetrics]); - - const hasTrialData = - data[split]?.TRIAL && Object.keys(data[split].TRIAL).length > 0; - const hasStepData = - data[split]?.STEP && Object.keys(data[split].STEP).length > 0; - const hasEpochData = - data[split]?.EPOCH && Object.keys(data[split].EPOCH).length > 0; + }, [selectedMetrics, filteredMetrics, theme]); + + // A split with no real curve at all (e.g. a non-iterative model with no + // hyperparameter optimization) only ever has a single final value - the + // fallback exposes it as if every level had that one point, same as the + // three toggle buttons below already do for a real curve. + const splitHasFallbackData = + !splitHasRealData && Object.keys(splitFallback?.TRIAL ?? {}).length > 0; + + const hasTrialData = splitHasRealData + ? Boolean(data[split]?.TRIAL && Object.keys(data[split].TRIAL).length > 0) + : splitHasFallbackData; + const hasStepData = splitHasRealData + ? Boolean(data[split]?.STEP && Object.keys(data[split].STEP).length > 0) + : splitHasFallbackData; + const hasEpochData = splitHasRealData + ? Boolean(data[split]?.EPOCH && Object.keys(data[split].EPOCH).length > 0) + : splitHasFallbackData; const levelLabel = useMemo(() => { if (!level) return ""; @@ -291,113 +289,268 @@ export function LiveMetricsChart({ run }) { } }, [split, level, filteredMetricKeys]); - const handleMetricChange = (e) => { - const newSelection = e.target.value; + const handleToggleMetric = (metric) => { + const canonicalOrder = Object.keys(filteredMetrics); + const newSelection = selectedMetrics.includes(metric) + ? selectedMetrics.filter((m) => m !== metric) + : canonicalOrder.filter( + (m) => m === metric || selectedMetrics.includes(m), + ); + setSelectedMetrics(newSelection); + selectedMetricsPerSplit.current[split] = newSelection; + }; + + const handleSelectAll = () => { + const newSelection = Object.keys(filteredMetrics); setSelectedMetrics(newSelection); selectedMetricsPerSplit.current[split] = newSelection; }; + const handleClearAll = () => { + setSelectedMetrics([]); + selectedMetricsPerSplit.current[split] = []; + }; + const handleLevelChange = (newLevel) => { setLevel(newLevel); }; return ( - - - - {t("models:label.metrics")} - - + + + - setSplit(v)} sx={{ mb: 4 }}> - - - - - - {chartData.length === 0 || selectedMetrics.length === 0 ? ( - + { + if (newValue !== null) setSplit(newValue); + }} + sx={{ + bgcolor: (theme) => alpha(theme.palette.ui.box, 0.8), + backdropFilter: "blur(8px)", + }} > - - {t("models:label.noMetricsAvailableForThisView")} - - - ) : ( - - - - - - - - {selectedMetrics.map((metric, idx) => ( - - ))} - - - )} - - - - - - - + {panels.map((panel) => { + let containerEl = null; + const plotData = [ + { + type: "scatter", + mode: panel.x.length > 1 ? "lines" : "markers", + x: panel.x, + y: panel.y, + line: { + color: panel.color, + width: 2, + shape: "spline", + smoothing: 0.7, + }, + marker: { color: panel.color }, + hovertemplate: "%{x}: %{y:.4f}", + }, + ]; + const plotLayout = { + autosize: true, + height: 240, + margin: { l: 50, r: 12, t: 8, b: 55 }, + showlegend: false, + paper_bgcolor: theme.palette.background.paper, + plot_bgcolor: theme.palette.background.paper, + font: { + color: theme.palette.text.primary, + family: theme.typography.fontFamily, + size: 11, + }, + xaxis: { + title: { text: levelLabel, standoff: 10 }, + gridcolor: theme.palette.divider, + zerolinecolor: theme.palette.divider, + tickfont: { + color: theme.palette.text.primary, + size: 10, + }, + }, + yaxis: { + gridcolor: theme.palette.divider, + tickfont: { + color: theme.palette.text.primary, + size: 10, + }, + automargin: true, + }, + }; + return ( + { + containerEl = node; + }} + sx={{ + border: 1, + borderColor: "divider", + borderRadius: 1, + p: 2, + "& .plot-actions": { + opacity: 0, + transition: "opacity 0.15s ease", + }, + "&:hover .plot-actions, &:focus-within .plot-actions": { + opacity: 1, + }, + "@media (hover: none)": { + "& .plot-actions": { opacity: 1 }, + }, + }} + > + + + {panel.metric} + + containerEl} + data={plotData} + layout={plotLayout} + filename={panel.metric} + /> + + + + ); + })} + + )} + + + + + + + + ); diff --git a/DashAI/front/src/components/models/ManualPredictionPanel.jsx b/DashAI/front/src/components/models/ManualPredictionPanel.jsx index 0431b4ab1..9dd63b2b4 100644 --- a/DashAI/front/src/components/models/ManualPredictionPanel.jsx +++ b/DashAI/front/src/components/models/ManualPredictionPanel.jsx @@ -158,78 +158,87 @@ export default function ManualPredictionPanel({ if (!manualRows || manualRows.length === 0) return; setIsSaving(true); try { - const prediction = await createPrediction(run.id, null); - const jobResponse = await enqueuePredictionJob(prediction.id, manualRows); - - if (!jobResponse || !jobResponse.id) { - throw new Error("Failed to enqueue prediction job"); - } + // Each manually entered row becomes its own prediction, so every row + // can later be viewed/deleted independently instead of all rows from + // one submission being tied to a single prediction record. + const submissions = await Promise.all( + manualRows.map(async (row) => { + const prediction = await createPrediction(run.id, null); + const jobResponse = await enqueuePredictionJob(prediction.id, [row]); + if (!jobResponse || !jobResponse.id) { + throw new Error("Failed to enqueue prediction job"); + } + return { prediction, jobId: jobResponse.id }; + }), + ); enqueueSnackbar(t("prediction:message.predictionJobSubmitted"), { variant: "success", }); - let optimisticPrediction = prediction; + let predictionsAfterEnqueue = []; try { - const predictionsAfterEnqueue = await getPredictions(run.id); - const freshlyCreated = predictionsAfterEnqueue.find( - (p) => p.id === prediction.id, - ); - optimisticPrediction = freshlyCreated || prediction; + predictionsAfterEnqueue = await getPredictions(run.id); } catch (refreshError) { console.error( - "Error refreshing prediction after enqueueing job:", + "Error refreshing predictions after enqueueing jobs:", refreshError, ); } - optimisticPrediction = { - ...optimisticPrediction, - status: optimisticPrediction.status ?? 1, - }; - - // Optimistically call onSaved so the card appears quickly - if (onSaved) onSaved(optimisticPrediction); onClose(); - // Poll in the background to update the card status - startJobPolling( - jobResponse.id, - async () => { - const updatedPredictions = await getPredictions(run.id); - const updatedPrediction = updatedPredictions.find( - (p) => p.id === prediction.id, - ); - enqueueSnackbar(t("prediction:message.predictionCompleted"), { - variant: "success", - }); - if (onSaved) onSaved(updatedPrediction || prediction); - }, - async (result) => { - console.error("Prediction job failed:", result); - enqueueSnackbar( - t("prediction:error.predictionFailed", { - error: result.error || t("common:unknownError"), - }), - { variant: "error" }, - ); + submissions.forEach(({ prediction, jobId }) => { + const freshlyCreated = predictionsAfterEnqueue.find( + (p) => p.id === prediction.id, + ); + const optimisticPrediction = { + ...(freshlyCreated || prediction), + status: (freshlyCreated || prediction).status ?? 1, + }; + + // Optimistically call onSaved so the card appears quickly + if (onSaved) onSaved(optimisticPrediction); - try { + // Poll in the background to update the card status + startJobPolling( + jobId, + async () => { const updatedPredictions = await getPredictions(run.id); const updatedPrediction = updatedPredictions.find( (p) => p.id === prediction.id, ); + enqueueSnackbar(t("prediction:message.predictionCompleted"), { + variant: "success", + }); if (onSaved) onSaved(updatedPrediction || prediction); - } catch (refreshError) { - console.error( - "Error refreshing prediction after job failure:", - refreshError, + }, + async (result) => { + console.error("Prediction job failed:", result); + enqueueSnackbar( + t("prediction:error.predictionFailed", { + error: result.error || t("common:unknownError"), + }), + { variant: "error" }, ); - } - }, - ); + + try { + const updatedPredictions = await getPredictions(run.id); + const updatedPrediction = updatedPredictions.find( + (p) => p.id === prediction.id, + ); + if (onSaved) onSaved(updatedPrediction || prediction); + } catch (refreshError) { + console.error( + "Error refreshing prediction after job failure:", + refreshError, + ); + } + }, + ); + }); } catch (error) { - console.error("Error saving prediction:", error); + console.error("Error saving predictions:", error); enqueueSnackbar(t("prediction:error.creatingPrediction"), { variant: "error", }); diff --git a/DashAI/front/src/components/models/ManualPredictionsTable.jsx b/DashAI/front/src/components/models/ManualPredictionsTable.jsx new file mode 100644 index 000000000..76646adcb --- /dev/null +++ b/DashAI/front/src/components/models/ManualPredictionsTable.jsx @@ -0,0 +1,642 @@ +import React, { + useState, + useEffect, + useCallback, + useMemo, + useRef, +} from "react"; +import PropTypes from "prop-types"; +import { + Box, + Button, + Typography, + CircularProgress, + IconButton, + Tooltip, +} from "@mui/material"; +import { LoadingButton } from "@mui/lab"; +import { + Close as CloseIcon, + Delete as DeleteIcon, + DisabledByDefaultOutlined as SelectRowsIcon, +} from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; +import DatasetTable from "../notebooks/dataset/DatasetTable"; +import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import InputField from "../predictions/InputField"; +import { + getDatasetFile, + getDatasetTypesByFilePath, + getDatasetTypes, + getDatasetSample, +} from "../../api/datasets"; +import { + createPrediction, + deletePrediction, + getPredictions, +} from "../../api/predict"; +import { enqueuePredictionJob } from "../../api/job"; +import { getModelSessionById } from "../../api/modelSession"; +import { startJobPolling } from "../../utils/jobPoller"; +import { + getTargetDecimals, + formatPredictionRows, +} from "../../utils/predictionFormat"; +import "../shared/leanDatasetTable/leanDatasetTable.css"; + +// Manual predictions are entered by hand, so each one only ever produces a +// handful of rows — fetching the full result in one call (instead of the +// paginated flow used for dataset predictions) is safe here. +const FETCH_ALL_PAGE_SIZE = 10000; + +// Stable reference for the "no columns yet" fallback below — a fresh `[]` +// literal on every render would invalidate the `editableRows` memo that +// depends on it for the entire loading window. +const EMPTY_ARRAY = []; + +function applyFilter(rows, filterModel) { + if (!filterModel?.items?.length) return rows; + return rows.filter((row) => + filterModel.items.every(({ field, operator, value }) => { + const cell = row[field]; + switch (operator) { + case "isEmpty": + return cell === null || cell === undefined || cell === ""; + case "isNotEmpty": + return cell !== null && cell !== undefined && cell !== ""; + case "equals": + return String(cell) === String(value); + case "contains": + return String(cell ?? "") + .toLowerCase() + .includes(String(value ?? "").toLowerCase()); + case "startsWith": + return String(cell ?? "") + .toLowerCase() + .startsWith(String(value ?? "").toLowerCase()); + case "endsWith": + return String(cell ?? "") + .toLowerCase() + .endsWith(String(value ?? "").toLowerCase()); + case "greaterThan": + return Number(cell) > Number(value); + case "greaterThanOrEqualTo": + return Number(cell) >= Number(value); + case "lessThan": + return Number(cell) < Number(value); + case "lessThanOrEqualTo": + return Number(cell) <= Number(value); + case "between": { + const [min, max] = value ?? []; + const num = Number(cell); + if (min != null && String(min).trim() !== "" && num < Number(min)) + return false; + if (max != null && String(max).trim() !== "" && num > Number(max)) + return false; + return true; + } + default: + return true; + } + }), + ); +} + +function applySort(rows, sortModel) { + if (!sortModel?.length) return rows; + const { id, desc } = sortModel[0]; + const sorted = [...rows].sort((a, b) => { + const av = a[id]; + const bv = b[id]; + if (av == null && bv == null) return 0; + if (av == null) return -1; + if (bv == null) return 1; + if (typeof av === "number" && typeof bv === "number") return av - bv; + return String(av).localeCompare(String(bv)); + }); + return desc ? sorted.reverse() : sorted; +} + +export default function ManualPredictionsTable({ + run, + session, + predictions, + targetColumn, + datasetSample, + onSaved, + onDelete, + actionsRef = null, + onStateChange = null, +}) { + const { t } = useTranslation(["prediction", "common"]); + const { enqueueSnackbar } = useSnackbar(); + const [allRows, setAllRows] = useState([]); + const [columnTypes, setColumnTypes] = useState({}); + const [loading, setLoading] = useState(true); + + // Row-selection mode: lets the user pick several finished predictions and + // delete them all at once, instead of a delete icon sitting on every row. + const [selectionMode, setSelectionMode] = useState(false); + const [selectedRowIndices, setSelectedRowIndices] = useState(() => new Set()); + const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false); + const [bulkDeleting, setBulkDeleting] = useState(false); + // Selection indices are positions in the filtered+sorted row list that + // `fetchPage` computes internally - this ref keeps the latest version of + // that list around so indices can be mapped back to real rows on delete. + const lastFilteredSortedRef = useRef([]); + + // Session metadata needed to render editable "add row" inputs. + const [modelSession, setModelSession] = useState(null); + const [inputTypes, setInputTypes] = useState({}); + const [inputSample, setInputSample] = useState(null); + const [loadingSession, setLoadingSession] = useState(true); + + // Each entry is `{ key, values, status: "draft" | "pending", predictionId }`. + // Rows stay in this same array across the whole draft -> pending -> real-row + // lifecycle so they never have to be removed and re-added elsewhere - once + // the real row lands in `allRows`, the entry is filtered out of + // `editableRows` (see below) instead of disappearing and popping back in. + const [manualEntries, setManualEntries] = useState([]); + const [isRunning, setIsRunning] = useState(false); + const nextEntryKeyRef = useRef(0); + + const finishedPredictions = useMemo( + () => predictions.filter((p) => p.status === 3), + [predictions], + ); + + const refetchRows = useCallback(() => { + if (finishedPredictions.length === 0) { + setAllRows([]); + setColumnTypes({}); + setLoading(false); + return; + } + + setLoading(true); + const targetDecimals = getTargetDecimals(datasetSample, targetColumn); + + // Newest prediction first, so it doesn't get pushed out of view. + const orderedPredictions = [...finishedPredictions].reverse(); + + Promise.all( + orderedPredictions.map((prediction) => + getDatasetFile(prediction.results_path, 0, FETCH_ALL_PAGE_SIZE).then( + (data) => { + const formatted = formatPredictionRows( + data.rows ?? [], + targetColumn, + targetDecimals, + ); + return formatted.map((row) => ({ + ...row, + __predictionId: prediction.id, + })); + }, + ), + ), + ) + .then((groups) => setAllRows(groups.flat())) + .catch(() => setAllRows([])) + .finally(() => setLoading(false)); + + getDatasetTypesByFilePath(finishedPredictions[0].results_path) + .then((types) => setColumnTypes(types)) + .catch(() => {}); + }, [finishedPredictions, targetColumn, datasetSample]); + + useEffect(() => { + refetchRows(); + }, [refetchRows]); + + // Fetch the model session + input column metadata once, so "Add Row" can + // render editable fields matching the model's expected input schema. + useEffect(() => { + let cancelled = false; + const fetchSessionData = async () => { + if (!run) return; + setLoadingSession(true); + try { + const sessionData = await getModelSessionById( + run.model_session_id || session?.id, + ); + if (cancelled) return; + setModelSession(sessionData); + const [types, sample] = await Promise.all([ + getDatasetTypes(sessionData.dataset_id), + getDatasetSample(sessionData.dataset_id), + ]); + if (cancelled) return; + setInputTypes(types); + setInputSample(sample); + } catch (error) { + console.error( + "Error loading session data for manual predictions:", + error, + ); + } finally { + if (!cancelled) setLoadingSession(false); + } + }; + fetchSessionData(); + return () => { + cancelled = true; + }; + }, [run, session]); + + const inputColumns = modelSession?.input_columns ?? EMPTY_ARRAY; + + const createEmptyRow = useCallback(() => { + if (!inputSample || inputColumns.length === 0) return {}; + const randomIndex = Math.floor( + Math.random() * inputSample[inputColumns[0]].length, + ); + const row = {}; + inputColumns.forEach((col) => { + const typeInfo = inputTypes[col]; + if (typeInfo?.type === "Image") { + row[col] = null; + } else if ( + typeInfo?.type === "Categorical" && + typeInfo?.categories?.length > 0 + ) { + row[col] = + typeInfo.categories[randomIndex % typeInfo.categories.length]; + } else { + row[col] = inputSample[col][randomIndex]; + } + }); + return row; + }, [inputColumns, inputTypes, inputSample]); + + const handleAddRow = () => { + const key = `manual-${nextEntryKeyRef.current++}`; + setManualEntries((prev) => [ + ...prev, + { key, values: createEmptyRow(), status: "draft" }, + ]); + }; + + const handleDraftChange = useCallback((key, col, value) => { + setManualEntries((prev) => + prev.map((entry) => + entry.key === key + ? { ...entry, values: { ...entry.values, [col]: value } } + : entry, + ), + ); + }, []); + + const handleDeleteDraftRow = (key) => { + setManualEntries((prev) => prev.filter((entry) => entry.key !== key)); + }; + + const draftEntries = useMemo( + () => manualEntries.filter((entry) => entry.status === "draft"), + [manualEntries], + ); + + // Once a pending entry's real row shows up in `allRows`, drop it from + // state - it's already excluded from `editableRows` below by then, so this + // is just cleanup and never causes a visual gap. + useEffect(() => { + setManualEntries((prev) => { + const next = prev.filter( + (entry) => + entry.status === "draft" || + !allRows.some((r) => r.__predictionId === entry.predictionId), + ); + return next.length === prev.length ? prev : next; + }); + }, [allRows]); + + const handleRunPrediction = async () => { + if (draftEntries.length === 0) return; + setIsRunning(true); + try { + // Each draft row becomes its own prediction, so every row can later + // be viewed/deleted independently. + const submissions = await Promise.all( + draftEntries.map(async (entry) => { + const prediction = await createPrediction(run.id, null); + const jobResponse = await enqueuePredictionJob(prediction.id, [ + entry.values, + ]); + if (!jobResponse || !jobResponse.id) { + throw new Error("Failed to enqueue prediction job"); + } + return { entry, prediction, jobId: jobResponse.id }; + }), + ); + + enqueueSnackbar(t("prediction:message.predictionJobSubmitted"), { + variant: "success", + }); + + // Flip the submitted rows to "pending" in place rather than clearing + // them, so they stay put (as read-only, spinner rows) until the real + // row is ready instead of disappearing and popping back in elsewhere. + setManualEntries((prev) => + prev.map((entry) => { + const match = submissions.find((s) => s.entry.key === entry.key); + return match + ? { ...entry, status: "pending", predictionId: match.prediction.id } + : entry; + }), + ); + + let predictionsAfterEnqueue = []; + try { + predictionsAfterEnqueue = await getPredictions(run.id); + } catch (refreshError) { + console.error( + "Error refreshing predictions after enqueueing jobs:", + refreshError, + ); + } + + submissions.forEach(({ entry, prediction, jobId }) => { + const freshlyCreated = predictionsAfterEnqueue.find( + (p) => p.id === prediction.id, + ); + const optimisticPrediction = { + ...(freshlyCreated || prediction), + status: (freshlyCreated || prediction).status ?? 1, + }; + if (onSaved) onSaved(optimisticPrediction); + + startJobPolling( + jobId, + async () => { + const updatedPredictions = await getPredictions(run.id); + const updatedPrediction = updatedPredictions.find( + (p) => p.id === prediction.id, + ); + enqueueSnackbar(t("prediction:message.predictionCompleted"), { + variant: "success", + }); + if (onSaved) onSaved(updatedPrediction || prediction); + }, + async (result) => { + console.error("Prediction job failed:", result); + enqueueSnackbar( + t("prediction:error.predictionFailed", { + error: result.error || t("common:unknownError"), + }), + { variant: "error" }, + ); + // This entry will never gain a real row, so it can't rely on the + // `allRows` cleanup effect - drop it here instead. + setManualEntries((prev) => prev.filter((e) => e.key !== entry.key)); + try { + const updatedPredictions = await getPredictions(run.id); + const updatedPrediction = updatedPredictions.find( + (p) => p.id === prediction.id, + ); + if (onSaved) onSaved(updatedPrediction || prediction); + } catch (refreshError) { + console.error( + "Error refreshing prediction after job failure:", + refreshError, + ); + } + }, + ); + }); + } catch (error) { + console.error("Error saving predictions:", error); + enqueueSnackbar(t("prediction:error.creatingPrediction"), { + variant: "error", + }); + } finally { + setIsRunning(false); + } + }; + + // Add Row / Run Prediction live in the parent's header row (next to the + // Dataset/Manual sub-selector) instead of this table's own toolbar, so both + // prediction types look consistent. The parent triggers them through this + // ref and reads button-disabled state through onStateChange. + useEffect(() => { + if (actionsRef) { + actionsRef.current = { + addRow: handleAddRow, + runPrediction: handleRunPrediction, + }; + } + }); + + useEffect(() => { + onStateChange?.({ + canAddRow: !loadingSession, + canRun: draftEntries.length > 0, + isRunning, + }); + }, [loadingSession, draftEntries.length, isRunning, onStateChange]); + + const extendedColumnTypes = useMemo( + () => (Object.keys(columnTypes).length > 0 ? columnTypes : inputTypes), + [columnTypes, inputTypes], + ); + + const editableRows = useMemo(() => { + // A pending entry keeps rendering here (instead of being removed) until + // its real row is actually present in `allRows` - see the cleanup effect + // above. + const visible = manualEntries.filter( + (entry) => + entry.status === "draft" || + !allRows.some((r) => r.__predictionId === entry.predictionId), + ); + return visible.map((entry) => ({ + key: entry.key, + renderCell: (colKey) => { + if (!inputColumns.includes(colKey)) { + if (entry.status === "pending") return ; + // No dedicated actions column for draft rows (it looked odd sitting + // mostly empty next to finished rows) - the target column has no + // result yet anyway, so the row's own cancel button lives there. + if (colKey === targetColumn) { + return ( + + + handleDeleteDraftRow(entry.key)} + sx={{ color: "#fff" }} + > + + + + + ); + } + return "—"; + } + if (entry.status === "pending") { + const value = entry.values[colKey]; + const display = + value instanceof File ? value.name : String(value ?? ""); + return ( + + {display} + + ); + } + return ( + + ); + }, + })); + }, [ + manualEntries, + allRows, + inputColumns, + inputTypes, + inputSample, + handleDraftChange, + ]); + + const fetchPage = useCallback( + async (page, pageSize, filterModel, sortModel) => { + let rows = applyFilter(allRows, filterModel); + rows = applySort(rows, sortModel); + lastFilteredSortedRef.current = rows; + const total = rows.length; + const start = page * pageSize; + return { rows: rows.slice(start, start + pageSize), total }; + }, + [allRows], + ); + + const handleExitSelectionMode = () => { + setSelectionMode(false); + setSelectedRowIndices(new Set()); + }; + + const handleBulkDeleteConfirm = async () => { + const targetIds = [ + ...new Set( + [...selectedRowIndices] + .map((index) => lastFilteredSortedRef.current[index]?.__predictionId) + .filter((id) => id != null), + ), + ]; + if (targetIds.length === 0) { + setBulkDeleteOpen(false); + return; + } + setBulkDeleting(true); + try { + await Promise.all(targetIds.map((id) => deletePrediction(id))); + enqueueSnackbar( + t("prediction:message.predictionsDeleted", { + count: targetIds.length, + }), + { variant: "success" }, + ); + setBulkDeleteOpen(false); + handleExitSelectionMode(); + if (onDelete) onDelete(); + } catch (error) { + console.error("Error deleting selected predictions:", error); + enqueueSnackbar(t("prediction:error.errorDeletingSelected"), { + variant: "error", + }); + } finally { + setBulkDeleting(false); + } + }; + + const selectionToolbarActions = selectionMode ? ( + <> + } + disabled={selectedRowIndices.size === 0} + loading={bulkDeleting} + onClick={() => setBulkDeleteOpen(true)} + sx={{ textTransform: "none", fontWeight: 500 }} + > + {t("prediction:button.deleteSelected", { + count: selectedRowIndices.size, + })} + + + + ) : ( + + setSelectionMode(true)}> + + + + ); + + return ( + + + + setBulkDeleteOpen(false)} + onConfirm={handleBulkDeleteConfirm} + content={t("prediction:label.confirmBulkDeletion", { + count: selectedRowIndices.size, + })} + /> + + ); +} + +ManualPredictionsTable.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number.isRequired, + model_session_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }).isRequired, + session: PropTypes.shape({ + id: PropTypes.number, + }), + predictions: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.number.isRequired, + status: PropTypes.number.isRequired, + created: PropTypes.string, + results_path: PropTypes.string, + }), + ).isRequired, + targetColumn: PropTypes.string, + datasetSample: PropTypes.object, + onSaved: PropTypes.func, + onDelete: PropTypes.func, + actionsRef: PropTypes.shape({ current: PropTypes.object }), + onStateChange: PropTypes.func, +}; diff --git a/DashAI/front/src/components/models/ModelCardCompact.jsx b/DashAI/front/src/components/models/ModelCardCompact.jsx new file mode 100644 index 000000000..c3a0ca955 --- /dev/null +++ b/DashAI/front/src/components/models/ModelCardCompact.jsx @@ -0,0 +1,254 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Paper, Box, Typography, IconButton, Tooltip } from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; +import { PlayArrow, Delete, ChevronRight, Edit } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { + canTrainRun, + isRunActive, + getRunStatusColor, +} from "../../utils/runStatus"; +import { ModelIcon } from "./model/ModelIcon"; +import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import RunEditDialog from "./RunEditDialog"; +import RunStatusDot from "../shared/RunStatusDot"; +import { useModelDownloadGate } from "./model/ComponentDownloadControl"; + +/** + * Compact launcher card for a single run — shows just enough to identify + * the model and its state, and opens the full-screen model detail view. + */ +function ModelCardCompact({ + run, + models = [], + session, + existingRuns = [], + onTrain, + onDelete, + onRefresh, + onOpen, + isHighlighted = false, +}) { + const theme = useTheme(); + const { t } = useTranslation(["models", "common"]); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); + + const model = models.find((m) => m.name === run.model_name); + const modelDisplayName = model?.display_name || run.model_name; + const canTrain = canTrainRun(run.status); + const isRunning = isRunActive(run.status); + + // A download-required model must be downloaded before it can be trained — + // otherwise clicking Train silently re-triggers a download for a model the + // user just deleted. Mirrors the same gate in RunCard. + const { modelNotDownloaded } = useModelDownloadGate(model, run.model_name); + + const statusColorKey = getRunStatusColor(run.status); + const statusMain = + statusColorKey === "default" + ? theme.palette.text.disabled + : theme.palette[statusColorKey].main; + + return ( + + + + + + + + + {run.name} + + + + + {modelDisplayName} + + + + + e.stopPropagation()} + > + {canTrain && ( + + + onTrain(run)} + > + + + + + )} + + setConfigOpen(true)}> + + + + + setDeleteConfirmOpen(true)} + > + + + + + + {/* Wrapped so clicks inside the modal (a portal) don't bubble through + the React tree into the card's onClick={onOpen} above */} + e.stopPropagation()}> + setDeleteConfirmOpen(false)} + onConfirm={() => { + setDeleteConfirmOpen(false); + localStorage.removeItem(`run-${run.id}-results-visible`); + localStorage.removeItem(`run-${run.id}-active-tab`); + onDelete(run); + }} + content={t("models:message.confirmDeleteRun")} + /> + + setConfigOpen(false)} + /> + + + + {/* Footer hint — signals the whole card is clickable to open the + full model detail view (configuration, metrics, predictions, etc.) */} + + + {t("models:label.metrics")} | {t("models:label.operations")} + + + + + ); +} + +ModelCardCompact.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + model_name: PropTypes.string, + status: PropTypes.number, + goal_metric: PropTypes.string, + trained_models: PropTypes.array, + }).isRequired, + models: PropTypes.array, + session: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + task_name: PropTypes.string, + }), + existingRuns: PropTypes.array, + onTrain: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onRefresh: PropTypes.func, + onOpen: PropTypes.func.isRequired, + isHighlighted: PropTypes.bool, +}; + +export default ModelCardCompact; diff --git a/DashAI/front/src/components/models/ModelComparisonTable.jsx b/DashAI/front/src/components/models/ModelComparisonTable.jsx index 7d8eed469..0ccde3f4a 100644 --- a/DashAI/front/src/components/models/ModelComparisonTable.jsx +++ b/DashAI/front/src/components/models/ModelComparisonTable.jsx @@ -5,30 +5,26 @@ import { useMaterialReactTable, } from "material-react-table"; import { useTheme } from "@mui/material/styles"; -import { - Box, - IconButton, - MenuItem, - Select, - Tooltip, - Typography, -} from "@mui/material"; +import { Box, IconButton, Tooltip } from "@mui/material"; import { PlayArrow, Delete, Visibility } from "@mui/icons-material"; import { getComponents } from "../../api/component"; import { useTranslation } from "react-i18next"; import { useTableLocalization } from "../../utils/useTableLocalization"; -import api from "../../api/api"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import { + getComponentDownloadState, + subscribeAnyDownloadState, +} from "./model/ComponentDownloadControl"; +import { canTrainRun, isRunActive } from "../../utils/runStatus"; /** * Compact comparison table showing all runs in a session. - * Designed for sticky header display with fixed height. + * Renders at its natural content height — the page scrolls, not the table. * * Scores are computed server-side and fetched from the backend. */ function ModelComparisonTable({ runs: initialRuns = [], - session, onTrain, onViewDetails, onDelete, @@ -37,12 +33,26 @@ function ModelComparisonTable({ }) { const [models, setModels] = useState([]); const [metrics, setMetrics] = useState([]); - const [profiles, setProfiles] = useState([]); - const [selectedProfile, setSelectedProfile] = useState(null); - const [scores, setScores] = useState({}); - const [loadingScores, setLoadingScores] = useState(false); const [runs, setRuns] = useState(initialRuns); const [runToDelete, setRunToDelete] = useState(null); + // Bump to re-render when a download finishes so the train button enables. + const [, setDownloadVersion] = useState(0); + + useEffect( + () => subscribeAnyDownloadState(() => setDownloadVersion((v) => v + 1)), + [], + ); + + // A run is trainable only if its model needs no download or the download is + // present and not in progress (live state overrides a stale fetched flag). + const isModelReady = (modelName) => { + const model = models.find((m) => m.name === modelName); + if (!model?.metadata?.requires_download) return true; + const cached = getComponentDownloadState(modelName); + const downloaded = cached?.downloaded ?? Boolean(model.downloaded); + const downloading = Boolean(cached?.downloading); + return downloaded && !downloading; + }; const { t, i18n } = useTranslation(["models", "common"]); const theme = useTheme(); @@ -84,84 +94,6 @@ function ModelComparisonTable({ fetchMetrics(); }, [i18n.language]); - // ──────────────────────────────────────────────────────────────────────── - // Fetch scoring profiles for this session's task - // ──────────────────────────────────────────────────────────────────────── - - useEffect(() => { - const fetchProfiles = async () => { - try { - const params = {}; - if (session?.task_name) { - params.task_name = session.task_name; - } - const response = await api.get("/v1/scoring/profiles", { params }); - const profilesList = response.data; - setProfiles(profilesList); - - // Keep current profile only if still valid; otherwise select first - setSelectedProfile((prevProfile) => { - if (profilesList.length === 0) { - return null; - } - const profileExists = profilesList.some((p) => p.id === prevProfile); - return profileExists ? prevProfile : profilesList[0].id; - }); - } catch (error) { - console.error("Error fetching scoring profiles:", error); - } - }; - fetchProfiles(); - }, [session?.task_name]); - - // Stable string that changes only when a run's status changes. - // Used as a dep so the score fetch re-triggers after training completes - // without firing on every unrelated re-render of the parent. - const runStatusSignature = useMemo( - () => initialRuns.map((r) => `${r.id}:${r.status}`).join(","), - [initialRuns], - ); - - // ──────────────────────────────────────────────────────────────────────── - // Fetch scores when profile, split, session or any run status changes - // ──────────────────────────────────────────────────────────────────────── - - useEffect(() => { - if (!initialRuns.length || !selectedProfile || !session?.id) return; - - const fetchScores = async () => { - setLoadingScores(true); - try { - const response = await api.get("/v1/run/", { - params: { - model_session_id: session.id, - include_scores: true, - profile_id: selectedProfile, - metric_split: metricSplit, - }, - }); - - // Update runs with metrics and scores - setRuns(response.data); - - // Extract scores into separate map for easy lookup - const scoresMap = {}; - response.data.forEach((run) => { - if (run.score) { - scoresMap[run.id] = run.score; - } - }); - setScores(scoresMap); - } catch (error) { - console.error("Error fetching scores:", error); - } finally { - setLoadingScores(false); - } - }; - - fetchScores(); - }, [selectedProfile, metricSplit, session?.id, runStatusSignature]); - // ──────────────────────────────────────────────────────────────────────── // Build columns // ──────────────────────────────────────────────────────────────────────── @@ -252,7 +184,7 @@ function ModelComparisonTable({ ), Cell: ({ row, cell }) => { const { status } = row.original; - const isRunning = status === 1 || status === 2; + const isRunning = isRunActive(status); if (isRunning) return "-"; const val = cell.getValue(); @@ -288,80 +220,6 @@ function ModelComparisonTable({ const data = useMemo(() => runs, [runs]); const columns = useMemo(() => { - const scoreColumn = { - id: "score", - header: t("models:label.score"), - size: 90, - accessorFn: (row) => scores[row.id]?.score ?? -1, - Header: () => ( - - - {t("models:label.score")} - - - ), - Cell: ({ row }) => { - const { status, id } = row.original; - const isRunning = status === 1 || status === 2; - if (isRunning) return "-"; - - const scoreData = scores[id]; - if (!scoreData) return "-"; - - const { score, breakdown } = scoreData; - - // Find the best score across all runs - const allScores = Object.values(scores) - .filter((s) => s && s.score !== undefined) - .map((s) => s.score); - const bestScore = allScores.length > 0 ? Math.max(...allScores) : null; - const isBest = bestScore !== null && Math.abs(score - bestScore) < 1e-6; - - const tooltipContent = ( - - - {t("models:label.score")}: {score.toFixed(1)}/100 - - {breakdown.map(({ metric_name, value, normalized_weight }, i) => ( - - {i === 0 ? "=" : "+"} {metric_name} ({value.toFixed(4)}) ×{" "} - {(normalized_weight * 100).toFixed(0)}% - - ))} - - ); - - return ( - - - {isBest && ( - - ★ - - )} - {score.toFixed(1)} - - - ); - }, - }; - return [ { accessorKey: "name", @@ -405,7 +263,6 @@ function ModelComparisonTable({ ), }, - scoreColumn, ...getMetricColumns(), { id: "actions", @@ -414,16 +271,19 @@ function ModelComparisonTable({ enableColumnFilter: false, size: 150, Cell: ({ row }) => { - const canTrain = - row.original.status === 0 || - row.original.status === 4 || - row.original.status === 3; - const isRunning = - row.original.status === 1 || row.original.status === 2; + const canTrain = canTrainRun(row.original.status); + const isRunning = isRunActive(row.original.status); + const modelReady = isModelReady(row.original.model_name); return ( - + r.id === row.original.id)); }} - disabled={!canTrain} + disabled={!canTrain || !modelReady} color="primary" > @@ -474,17 +334,7 @@ function ModelComparisonTable({ }, }, ]; - }, [ - models, - metrics, - runs, - scores, - metricSplit, - t, - onTrain, - onViewDetails, - onDelete, - ]); + }, [models, metrics, runs, metricSplit, t, onTrain, onViewDetails, onDelete]); const columnOrder = useMemo( () => columns.map((col) => col.id ?? col.accessorKey).filter(Boolean), @@ -498,17 +348,15 @@ function ModelComparisonTable({ muiTablePaperProps: { elevation: 0, sx: { - height: "100%", display: "flex", flexDirection: "column", border: "1px solid", borderColor: "divider", }, }, - muiTableContainerProps: { sx: { flex: 1, overflow: "auto" } }, localization, initialState: { density: "compact" }, - enableStickyHeader: true, + enableStickyHeader: false, enableRowSelection: false, enablePagination: false, enableTopToolbar: false, @@ -526,74 +374,14 @@ function ModelComparisonTable({ }), }); - const activeProfile = profiles.find((p) => p.id === selectedProfile); - const profileWeightsLabel = activeProfile - ? Object.entries(activeProfile.weights) - .map(([metric, w]) => `${metric}: ${(w * 100).toFixed(0)}%`) - .join(" · ") - : ""; - return ( - {/* Profile selector */} - - - {t("models:label.scoreProfile")}: - - - - {profileWeightsLabel} - - {loadingScores && ( - - {t("common:loading")} - - )} - - {/* Table */} @@ -614,11 +402,6 @@ function ModelComparisonTable({ ModelComparisonTable.propTypes = { runs: PropTypes.array.isRequired, - session: PropTypes.shape({ - id: PropTypes.number, - name: PropTypes.string, - task_name: PropTypes.string, - }), onTrain: PropTypes.func.isRequired, onViewDetails: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, diff --git a/DashAI/front/src/components/models/ModelConfigurationContent.jsx b/DashAI/front/src/components/models/ModelConfigurationContent.jsx new file mode 100644 index 000000000..dbc919735 --- /dev/null +++ b/DashAI/front/src/components/models/ModelConfigurationContent.jsx @@ -0,0 +1,79 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import ParamInfoList from "./ParamInfoBox"; + +/** + * Shared body for a run's "Configuración" view — the parameters it was + * trained with, plus its optimizer setup if it was tuned via HPO. Used both + * as a tab inside RunResults and as a standalone dialog opened from the + * compact model card. + */ +function ModelConfigurationContent({ run, model }) { + const { t } = useTranslation(["models", "common"]); + const paramProperties = model?.schema?.properties ?? {}; + const getParamLabel = (key) => paramProperties[key]?.title ?? key; + + const hasParams = run.parameters && Object.keys(run.parameters).length > 0; + const hasOptimizer = run.optimizer_name && run.goal_metric; + + if (!hasParams && !hasOptimizer) { + return ( + + {t("models:label.noConfigurationAvailable")} + + ); + } + + return ( + + {hasParams && ( + + + {t("models:label.modelConfiguration")} + + [ + getParamLabel(key), + value, + ])} + /> + + )} + + {hasOptimizer && ( + + + {t("common:optimizer")}: {run.optimizer_name} + + {run.optimizer_parameters && + Object.keys(run.optimizer_parameters).length > 0 && ( + [key, value], + )} + /> + )} + + {t("models:label.goalMetric")}: {run.goal_metric} + + + )} + + ); +} + +ModelConfigurationContent.propTypes = { + run: PropTypes.shape({ + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + model: PropTypes.shape({ + schema: PropTypes.object, + }), +}; + +export default ModelConfigurationContent; diff --git a/DashAI/front/src/components/models/ModelDetailView.jsx b/DashAI/front/src/components/models/ModelDetailView.jsx new file mode 100644 index 000000000..265536395 --- /dev/null +++ b/DashAI/front/src/components/models/ModelDetailView.jsx @@ -0,0 +1,191 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, Button, IconButton, Tooltip } from "@mui/material"; +import { PlayArrow, Delete, Edit } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import ModelsBreadcrumbs from "./ModelsBreadcrumbs"; +import RunCard from "./RunCard"; +import RunEditDialog from "./RunEditDialog"; +import RunStatusDot from "../shared/RunStatusDot"; +import { useModelDownloadGate } from "./model/ComponentDownloadControl"; +import { canTrainRun, isRunActive } from "../../utils/runStatus"; + +/** + * Full-screen detail view for a single model run: header with the run's + * identity and actions, an edit dialog for its parameters, and RunCard's + * tabs below. The column layout keeps the header fixed and lets RunCard (in + * fillHeight mode) own the scroll. + */ +export default function ModelDetailView({ + run, + models = [], + session, + onTrain, + onDelete, + explainerRefreshTrigger, + onOperationsRefresh, + existingRuns = [], + onRefresh, +}) { + const { t } = useTranslation(["models", "common"]); + const [editModalOpen, setEditModalOpen] = useState(false); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + + const model = models.find((m) => m.name === run.model_name); + const modelDisplayName = model?.display_name || run.model_name; + const canTrain = canTrainRun(run.status); + const isRunning = isRunActive(run.status); + + // A download-required model must be downloaded before it can be trained — + // otherwise clicking Train silently re-triggers a download for a model the + // user just deleted. Mirrors the same gate in RunCard. + const { modelNotDownloaded } = useModelDownloadGate(model, run.model_name); + + return ( + + + + + + + + {run.name} + + + {modelDisplayName} + + + + + + {canTrain && ( + + + + + + )} + + setEditModalOpen(true)}> + + + + + setDeleteConfirmOpen(true)} + > + + + + + + + + + + + + setEditModalOpen(false)} + /> + + ); +} + +ModelDetailView.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + model_name: PropTypes.string, + status: PropTypes.number, + created: PropTypes.string, + start_time: PropTypes.string, + end_time: PropTypes.string, + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + models: PropTypes.array, + session: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + task_name: PropTypes.string, + }), + onTrain: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + explainerRefreshTrigger: PropTypes.number, + onOperationsRefresh: PropTypes.func, + existingRuns: PropTypes.array, + onRefresh: PropTypes.func, +}; diff --git a/DashAI/front/src/components/models/ModelParamBlock.jsx b/DashAI/front/src/components/models/ModelParamBlock.jsx deleted file mode 100644 index 35df1822b..000000000 --- a/DashAI/front/src/components/models/ModelParamBlock.jsx +++ /dev/null @@ -1,110 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { - Box, - Collapse, - Table, - TableBody, - TableCell, - TableRow, - Typography, -} from "@mui/material"; -import { ExpandLess, ExpandMore } from "@mui/icons-material"; - -// Unwrap properties wrappers and single-entry model params to reach the leaf model. -// e.g. {properties: {component: "TaskModel", params: {comp: {component: "SVC", params: {...}}}}} -// → {component: "SVC", params: {...}} -function unwrapToLeafModel(value) { - if (typeof value !== "object" || value === null) return value; - if ("properties" in value) return unwrapToLeafModel(value.properties); - if ("component" in value && "params" in value) { - const entries = Object.entries(value.params); - if (entries.length === 1) { - const inner = entries[0][1]; - if ( - typeof inner === "object" && - inner !== null && - "component" in inner && - "params" in inner - ) { - return unwrapToLeafModel(inner); - } - } - } - return value; -} - -export function renderParamValue(value) { - if (typeof value !== "object" || value === null) { - return String(value); - } - if ("fixed_value" in value) { - return String(value.fixed_value); - } - const unwrapped = unwrapToLeafModel(value); - if ( - typeof unwrapped === "object" && - unwrapped !== null && - "component" in unwrapped && - "params" in unwrapped - ) { - return ( - - ); - } - return JSON.stringify(value); -} - -function ModelParamBlock({ component, params }) { - const [open, setOpen] = React.useState(false); - return ( - - setOpen((v) => !v)} - sx={{ - display: "inline-flex", - alignItems: "center", - gap: 1, - cursor: "pointer", - userSelect: "none", - "&:hover": { opacity: 0.75 }, - }} - > - - {component} - - {open ? ( - - ) : ( - - )} - - - - - {Object.entries(params).map(([k, v]) => ( - - - {k} - - - {renderParamValue(v)} - - - ))} - -
-
-
- ); -} - -ModelParamBlock.propTypes = { - component: PropTypes.string.isRequired, - params: PropTypes.object.isRequired, -}; - -export default ModelParamBlock; diff --git a/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx b/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx index 27d216085..477ebc988 100644 --- a/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx +++ b/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx @@ -13,7 +13,7 @@ export default function ModelsBreadcrumbs() { const location = useLocation(); const params = useParams(); const { t } = useTranslation(["models", "common"]); - const { datasets, sessions, tasks } = useModels(); + const { datasets, sessions, tasks, runs } = useModels(); const rootCrumb = { label: t("common:models"), path: "/app/models" }; @@ -47,6 +47,22 @@ export default function ModelsBreadcrumbs() { ]; } + if (path.includes("/model/") && params.id && params.runId) { + const session = sessions.find((s) => s.id === Number(params.id)); + const sessionName = session?.name ?? `#${params.id}`; + const run = runs.find((r) => r.id === Number(params.runId)); + const runName = run?.name ?? `#${params.runId}`; + return [ + rootCrumb, + { + label: sessionName, + path: `/app/models/sessions/${params.id}`, + current: false, + }, + { label: runName, path: null, current: true }, + ]; + } + if (path.startsWith("/app/models/sessions/") && params.id) { const session = sessions.find((s) => s.id === Number(params.id)); const name = session?.name ?? `#${params.id}`; diff --git a/DashAI/front/src/components/models/ModelsContext.jsx b/DashAI/front/src/components/models/ModelsContext.jsx index 2c092e26e..93298eaf4 100644 --- a/DashAI/front/src/components/models/ModelsContext.jsx +++ b/DashAI/front/src/components/models/ModelsContext.jsx @@ -4,6 +4,7 @@ import { useState, useCallback, useEffect, + useMemo, } from "react"; import { useTranslation } from "react-i18next"; import { useDatasets } from "../../hooks/datasets/useDatasets"; @@ -84,10 +85,29 @@ export function ModelsProvider({ children }) { const [selectedModel, setSelectedModel] = useState(null); const [configOpen, setConfigOpen] = useState(false); const [step, setStep] = useState(0); + const [activeRunId, setActiveRunId] = useState(null); const [selectedOption, setSelectedOption] = useState(OptionsEnum.NEW); const [datasetInfo, setDatasetInfo] = useState(null); const [datasetTab, setDatasetTab] = useState(0); const [sessionRightContent, setSessionRightContent] = useState(null); + const [runDetailTab, setRunDetailTab] = useState(null); + const [explainerRefreshTrigger, setExplainerRefreshTrigger] = useState(0); + const [explainerToCreate, setExplainerToCreate] = useState(null); + + const triggerExplainerRefresh = useCallback(() => { + setExplainerRefreshTrigger((prev) => prev + 1); + }, []); + + // Open the explainer creation dialog for a given {scope, name}. Shared so both + // the sidebar (click) and the central view (drag and drop) can trigger it, + // mirroring how selectModel opens the add model dialog. + const openExplainerCreator = useCallback((explainer) => { + setExplainerToCreate(explainer); + }, []); + + const closeExplainerCreator = useCallback(() => { + setExplainerToCreate(null); + }, []); const selectModel = useCallback((model) => { setSelectedModel(model); @@ -108,72 +128,146 @@ export function ModelsProvider({ children }) { fetchTasks(); }, [i18n.language]); - const value = { - selectedModel, - configOpen, - selectModel, - closeConfig, - setSelectedModel, - setConfigOpen, - datasets, - createDataset, - selectedDatasetId, - fetchDatasets, - selectDataset, - clearSelectedDataset, - deleteDataset, - deleteDatasetById, - editDataset, - addDatasetOptimistically, - replaceDatasets, - startDatasetPolling, - moveDatasetToFolder, - folders, - fetchFolders, - createFolder, - renameFolder, - deleteFolderById, - tasks, - loadingTasks, - selectedTask, - selectedSessionId, - selectedSession, - sessions, - setSessions, - fetchSessions, - fetchTasks, - editSession, - deleteSessionById, - setSelectedTask, - setSelectedSessionId, - setSelectedSession, - step, - setStep, - runs, - setRuns, - retrainDialogOpen, - setRetrainDialogOpen, - runToRetrain, - setRunToRetrain, - operationsCount, - setOperationsCount, - fetchRuns, - executeTraining, - onRunCreated, - onTrainRun, - onEditRun, - onDeleteRun, - handleCancelRetrain, - handleConfirmRetrain, - lastAddedRunId, - clearLastAddedRunId, - datasetInfo, - setDatasetInfo, - datasetTab, - setDatasetTab, - sessionRightContent, - setSessionRightContent, - }; + // Memoized — this context wraps the entire models page tree, so a fresh + // object literal every render would force every consumer (RunCard, + // ModelDetailView, ModelsRightBar, ...) to re-render whenever ANY field + // here changed, not just the one(s) a given consumer actually reads. + const value = useMemo( + () => ({ + selectedModel, + configOpen, + selectModel, + closeConfig, + setSelectedModel, + setConfigOpen, + datasets, + createDataset, + selectedDatasetId, + fetchDatasets, + selectDataset, + clearSelectedDataset, + deleteDataset, + deleteDatasetById, + editDataset, + addDatasetOptimistically, + replaceDatasets, + startDatasetPolling, + moveDatasetToFolder, + folders, + fetchFolders, + createFolder, + renameFolder, + deleteFolderById, + tasks, + loadingTasks, + selectedTask, + selectedSessionId, + selectedSession, + sessions, + setSessions, + fetchSessions, + fetchTasks, + editSession, + deleteSessionById, + setSelectedTask, + setSelectedSessionId, + setSelectedSession, + step, + setStep, + activeRunId, + setActiveRunId, + runs, + setRuns, + retrainDialogOpen, + setRetrainDialogOpen, + runToRetrain, + setRunToRetrain, + operationsCount, + setOperationsCount, + fetchRuns, + executeTraining, + onRunCreated, + onTrainRun, + onEditRun, + onDeleteRun, + handleCancelRetrain, + handleConfirmRetrain, + lastAddedRunId, + clearLastAddedRunId, + datasetInfo, + setDatasetInfo, + datasetTab, + setDatasetTab, + sessionRightContent, + setSessionRightContent, + runDetailTab, + setRunDetailTab, + explainerRefreshTrigger, + triggerExplainerRefresh, + explainerToCreate, + openExplainerCreator, + closeExplainerCreator, + }), + [ + selectedModel, + configOpen, + selectModel, + closeConfig, + datasets, + createDataset, + selectedDatasetId, + fetchDatasets, + selectDataset, + clearSelectedDataset, + deleteDataset, + deleteDatasetById, + editDataset, + addDatasetOptimistically, + replaceDatasets, + startDatasetPolling, + moveDatasetToFolder, + folders, + fetchFolders, + createFolder, + renameFolder, + deleteFolderById, + tasks, + loadingTasks, + selectedTask, + selectedSessionId, + selectedSession, + sessions, + fetchSessions, + fetchTasks, + editSession, + deleteSessionById, + step, + activeRunId, + runs, + retrainDialogOpen, + runToRetrain, + operationsCount, + fetchRuns, + executeTraining, + onRunCreated, + onTrainRun, + onEditRun, + onDeleteRun, + handleCancelRetrain, + handleConfirmRetrain, + lastAddedRunId, + clearLastAddedRunId, + datasetInfo, + datasetTab, + sessionRightContent, + runDetailTab, + explainerRefreshTrigger, + triggerExplainerRefresh, + explainerToCreate, + openExplainerCreator, + closeExplainerCreator, + ], + ); return ( {children} diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index 1d87f889e..36a05996b 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -4,23 +4,74 @@ import { Box, Typography, TextField, CircularProgress } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { Search as SearchIcon } from "@mui/icons-material"; import { useSnackbar } from "notistack"; +import { useParams } from "react-router-dom"; import SideBar from "../threeSectionLayout/panelContainers/SideBar"; import { getComponents } from "../../api/component"; import ModelListItem from "./model/ModelListItem"; +import { + startComponentDownload, + subscribeAnyDownloadState, + useComponentDownloadState, +} from "./model/ComponentDownloadControl"; +import ModelDownloadStatusIcon from "./model/ModelDownloadStatusIcon"; + +/** + * A single model row whose disabled state and download icon both derive from + * the shared live download state, so they never disagree. While a download is + * in progress the row stays disabled even if the backend already reports the + * (partially written) files as present. + */ +function ModelRow({ model, onUse, onDownload, dataTour }) { + const requiresDownload = Boolean(model.metadata?.requires_download); + const { downloaded, downloading } = useComponentDownloadState(model); + const ready = !requiresDownload || (downloaded && !downloading); + + const handleClick = () => { + if (downloading) return; + if (ready) onUse(model); + else onDownload(model); + }; + + return ( + : null + } + /> + ); +} + +ModelRow.propTypes = { + model: PropTypes.object.isRequired, + onUse: PropTypes.func.isRequired, + onDownload: PropTypes.func.isRequired, + dataTour: PropTypes.string, +}; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { useModels } from "./ModelsContext"; import AddModelDialog from "./AddModelDialog"; import ColumnInsights from "../notebooks/dataset/ColumnInsights"; +import RunInfoSidebar from "./RunInfoSidebar"; +import ExplainersSidebar from "../explainers/ExplainersSidebar"; + +const EXPLAINERS_TAB = 1; export default function ModelsRightBar({ onToggle }) { const theme = useTheme(); + const params = useParams(); + const isInModelDetail = Boolean(params.runId); const [models, setModels] = useState([]); const [filteredModels, setFilteredModels] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const { t } = useTranslation(["models"]); + const { t } = useTranslation(["models", "common"]); const { selectedSession: session, @@ -33,6 +84,10 @@ export default function ModelsRightBar({ onToggle }) { datasetInfo, setDatasetTab, sessionRightContent, + runDetailTab, + triggerExplainerRefresh, + datasets, + tasks, } = useModels(); const fetchModels = React.useCallback(async () => { @@ -64,6 +119,24 @@ export default function ModelsRightBar({ onToggle }) { } }, [session, fetchModels]); + // When any download finishes (or is deleted) update just that model's flag in + // place. A full refetch would flip `loading`, swap the list for a spinner and + // reset the scroll position; an in-place update keeps the list mounted and + // keeps `downloaded` accurate for the model passed on to the config dialog. + useEffect(() => { + if (!session) return undefined; + return subscribeAnyDownloadState((name, state) => { + if (state.downloaded === undefined) return; + setModels((prev) => + prev.map((model) => + model.name === name + ? { ...model, downloaded: state.downloaded } + : model, + ), + ); + }); + }, [session]); + // Filter models based on search useEffect(() => { if (searchQuery.trim() === "") { @@ -82,7 +155,7 @@ export default function ModelsRightBar({ onToggle }) { const tourContext = useTourContext(); - const handleModelClick = (model) => { + const handleUseModel = (model) => { if (!session) { enqueueSnackbar(t("models:error.selectSessionFirst"), { variant: "warning", @@ -103,6 +176,51 @@ export default function ModelsRightBar({ onToggle }) { } }; + const activeRun = isInModelDetail + ? existingRuns.find((r) => String(r.id) === params.runId) + : null; + + if (isInModelDetail && activeRun) { + // On the explainers tab of a finished run, offer the compatible + // explainers to add, mirroring how the session view offers models. + if (runDetailTab === EXPLAINERS_TAB && activeRun.status === 3) { + return ( + + ); + } + const activeModel = models.find((m) => m.name === activeRun.model_name); + const datasetName = datasets.find( + (d) => d.id === session?.dataset_id, + )?.name; + return ( + + ); + } + + const handleDownloadModel = (model) => { + if (!session) { + enqueueSnackbar(t("models:error.selectSessionFirst"), { + variant: "warning", + }); + return; + } + // Completion is reflected by the shared download-state subscription above, + // which updates the model's flag in place without a scroll-resetting + // refetch. + startComponentDownload({ component: model, enqueueSnackbar, t }); + }; + if (sessionRightContent) { return ( @@ -183,6 +301,23 @@ export default function ModelsRightBar({ onToggle }) {
) + ) : isInModelDetail ? ( + + + {t("models:label.exitModelDetailToAddModels")} + + ) : ( <> {/* Search Box */} @@ -237,11 +372,12 @@ export default function ModelsRightBar({ onToggle }) { ) : ( {filteredModels.map((model, index) => ( - handleModelClick(model)} - data-tour={index === 0 ? "first-model" : undefined} + onUse={handleUseModel} + onDownload={handleDownloadModel} + dataTour={index === 0 ? "first-model" : undefined} /> ))} @@ -255,6 +391,7 @@ export default function ModelsRightBar({ onToggle }) { open={configOpen} onClose={closeConfig} preselectedModel={selectedModel?.name} + preselectedModelObject={selectedModel} session={session} existingRuns={existingRuns} onRunCreated={onRunCreated} diff --git a/DashAI/front/src/components/models/ParamInfoBox.jsx b/DashAI/front/src/components/models/ParamInfoBox.jsx new file mode 100644 index 000000000..efc7affdd --- /dev/null +++ b/DashAI/front/src/components/models/ParamInfoBox.jsx @@ -0,0 +1,143 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Collapse, Paper, Typography } from "@mui/material"; +import { ExpandLess, ExpandMore } from "@mui/icons-material"; + +// Unwrap properties wrappers and single-entry model params to reach the leaf model. +// e.g. {properties: {component: "TaskModel", params: {comp: {component: "SVC", params: {...}}}}} +// → {component: "SVC", params: {...}} +function unwrapToLeafModel(value) { + if (typeof value !== "object" || value === null) return value; + if ("properties" in value) return unwrapToLeafModel(value.properties); + if ("component" in value && "params" in value) { + const entries = Object.entries(value.params); + if (entries.length === 1) { + const inner = entries[0][1]; + if ( + typeof inner === "object" && + inner !== null && + "component" in inner && + "params" in inner + ) { + return unwrapToLeafModel(inner); + } + } + } + return value; +} + +function asNestedComponent(value) { + if (typeof value !== "object" || value === null) return null; + if ("fixed_value" in value) return null; + const unwrapped = unwrapToLeafModel(value); + if ( + unwrapped && + typeof unwrapped === "object" && + "component" in unwrapped && + "params" in unwrapped + ) { + return unwrapped; + } + return null; +} + +function formatLeafValue(value) { + if (typeof value !== "object" || value === null) return String(value); + if ("fixed_value" in value) return String(value.fixed_value); + return JSON.stringify(value); +} + +/** + * One parameter as its own small card - same shell as the editable + * parameter cards used when configuring a model (bordered, rounded, bold + * label in a header row), just more compact since there's no input/toggle + * to fit, and read-only. The label sits on its own header row, never on + * the same line as the value, so long labels/values never compete for + * horizontal space. + * + * When the value is itself a nested component (a classifier plugged into + * another model, an optimizer's own sub-parameters, etc.) the header + * becomes clickable and collapses/expands that component's own parameters, + * each rendered as this same card - so a parameter nested arbitrarily many + * levels deep still renders (and collapses) correctly, not just one level. + */ +export function ParamInfoBox({ label, value }) { + const [open, setOpen] = useState(false); + const nested = asNestedComponent(value); + + return ( + + setOpen((v) => !v) : undefined} + sx={{ + px: 3, + py: 1.5, + display: "flex", + alignItems: "center", + gap: 2, + borderBottom: !nested || open ? "1px solid" : "none", + borderColor: "divider", + cursor: nested ? "pointer" : "default", + userSelect: nested ? "none" : "auto", + "&:hover": nested ? { bgcolor: "action.hover" } : undefined, + }} + > + + {label} + + {nested && ( + <> + + {nested.component} + + {open ? ( + + ) : ( + + )} + + )} + + + {nested ? ( + + + {Object.entries(nested.params).map(([key, val]) => ( + + ))} + + + ) : ( + + + {formatLeafValue(value)} + + + )} + + ); +} + +ParamInfoBox.propTypes = { + label: PropTypes.node.isRequired, + value: PropTypes.any, +}; + +/** Stacked list of ParamInfoBox, one per [label, value] pair. */ +export default function ParamInfoList({ rows }) { + return ( + + {rows.map(([label, value]) => ( + + ))} + + ); +} + +ParamInfoList.propTypes = { + rows: PropTypes.arrayOf(PropTypes.array).isRequired, +}; diff --git a/DashAI/front/src/components/models/PredictionCard.jsx b/DashAI/front/src/components/models/PredictionCard.jsx index ca85a7d66..36d7cf699 100644 --- a/DashAI/front/src/components/models/PredictionCard.jsx +++ b/DashAI/front/src/components/models/PredictionCard.jsx @@ -1,26 +1,18 @@ import React, { useState, useCallback, useEffect } from "react"; import PropTypes from "prop-types"; import { - Card, - CardContent, Typography, IconButton, - Chip, Box, Tooltip, - Button, - Collapse, CircularProgress, } from "@mui/material"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import RunStatusDot from "../shared/RunStatusDot"; import { - ExpandMore as ExpandMoreIcon, - ExpandLess as ExpandLessIcon, Delete as DeleteIcon, Download as DownloadIcon, - Dataset as DatasetIcon, } from "@mui/icons-material"; -import { getPredictionStatus } from "../../utils/predictionStatus"; import { deletePrediction } from "../../api/predict"; import { useSnackbar } from "notistack"; import DatasetTable from "../notebooks/dataset/DatasetTable"; @@ -49,25 +41,14 @@ export default function PredictionCard({ onUpdate, targetColumn = null, datasetSample = null, + displayNumber = null, }) { - const [expanded, setExpanded] = useState(() => { - const saved = localStorage.getItem(`prediction-${prediction.id}-expanded`); - return saved !== null ? JSON.parse(saved) : true; - }); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [columnTypes, setColumnTypes] = useState({}); const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(["prediction", "datasets", "common"]); - // Persist expanded state - useEffect(() => { - localStorage.setItem( - `prediction-${prediction.id}-expanded`, - JSON.stringify(expanded), - ); - }, [expanded, prediction.id]); - // Fetch column types when results path changes useEffect(() => { if (!prediction?.results_path) return; @@ -78,31 +59,9 @@ export default function PredictionCard({ const statusText = prediction.status; - // Status color mapping - const getStatusColor = (status) => { - switch (status) { - case 0: // Not Started - return "default"; - case 1: // Delivered - case 2: // Started - return "info"; - case 3: // Finished - return "success"; - case 4: // Error - return "error"; - default: - return "default"; - } - }; - const isRunning = RUNNING_STATUSES.includes(statusText); const isFinished = statusText === 3; // Finished - const formatDate = (dateString) => { - if (!dateString) return "N/A"; - return new Date(dateString).toLocaleString(); - }; - const handleDelete = async () => { try { await deletePrediction(prediction.id); @@ -177,147 +136,96 @@ export default function PredictionCard({ return ( <> - - - {/* Header with status and dataset info */} + + {/* Header with status and dataset info */} + + + + {t("prediction:label.prediction")} # + {displayNumber ?? prediction.id} + + + + + + + + + + + + + + setDeleteDialogOpen(true)} + disabled={isRunning} + color="error" + > + + + + + + + + {/* Results */} + {isFinished && ( + + + + )} + + {/* Show loading indicator if prediction is running */} + {isRunning && ( - - - {t("prediction:label.prediction")} #{prediction.id} - - - {formatDate(prediction.created)} - - {prediction.dataset_id && ( - - - - {prediction.dataset?.name || - t("datasets:label.unknownDataset")} - - - )} - - - - - - - - - - - - - setDeleteDialogOpen(true)} - disabled={isRunning} - color="error" - > - - - - - + + + {t("prediction:label.predictionInProgress")} + - - {/* Expandable Results */} - {isFinished && ( - - - - - - - {t("prediction:label.resultsPreview")} - - - - - - - - )} - - {/* Show loading indicator if prediction is running */} - {isRunning && ( - - - - {t("prediction:label.predictionInProgress")} - - - )} - - + )} +
0 || operationsCount.predictions > 0); return ( - - - - {hasOperations && } - - {mode === "save" - ? t("models:label.saveParameterChanges") - : t("models:label.retrainModel")} - + + + {/* Header */} + + + {hasOperations && } + + {mode === "save" + ? t("models:label.saveParameterChanges") + : t("models:label.retrainModel")} + + + + + - - - {hasOperations ? ( - <> - - {mode === "save" - ? t("models:message.saveWillDeleteOperations") - : t("models:label.retrainWillDeleteOperations")} - - + {/* Content */} + + {hasOperations ? ( + <> + + {mode === "save" + ? t("models:message.saveWillDeleteOperations") + : t("models:label.retrainWillDeleteOperations")} + + + {mode === "save" ? ( + + Saving "{{ runName: run?.name }}" will + reset the run. The following will be deleted when you train + again: + + ) : ( + + Re-training run "{{ runName: run?.name }}" + will delete: + + )} + + + {operationsCount.explainers > 0 && ( + + + • {{ count: operationsCount.explainers }}{" "} + explainer + + + )} + {operationsCount.predictions > 0 && ( + + + •{" "} + {{ count: operationsCount.predictions }}{" "} + prediction + + + )} + + + {t("models:label.operationsWillBeDeletedWarning")} + + + ) : ( + {mode === "save" ? ( - + Saving "{{ runName: run?.name }}" will reset - the run. The following will be deleted when you train again: + its status to 'Not Started' and clear its current metrics and + results. Are you sure you want to continue? ) : ( - - Re-training run "{{ runName: run?.name }}" - will delete: + + Are you sure you want to re-train run " + {{ runName: run?.name }} + "? )} - - - {operationsCount.explainers > 0 && ( - - - • {{ count: operationsCount.explainers }}{" "} - explainer - - - )} - {operationsCount.predictions > 0 && ( - - - • {{ count: operationsCount.predictions }}{" "} - prediction - - - )} - - - {t("models:label.operationsWillBeDeletedWarning")} - - - ) : ( - - - Are you sure you want to re-train run " - {{ runName: run?.name }} - "? - - - )} - + + )} - - - - - + {/* Footer */} + + + + +
+
+ ); } diff --git a/DashAI/front/src/components/models/RunCard.jsx b/DashAI/front/src/components/models/RunCard.jsx index f4f2b18f3..0e56362d2 100644 --- a/DashAI/front/src/components/models/RunCard.jsx +++ b/DashAI/front/src/components/models/RunCard.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import { Card, @@ -9,21 +9,7 @@ import { IconButton, Button, Collapse, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Paper, - Divider, Tooltip, - TextField, - Alert, - Dialog, - DialogTitle, - DialogContent, - DialogActions, } from "@mui/material"; import { useTheme, alpha } from "@mui/material/styles"; import { @@ -31,26 +17,21 @@ import { Stop, Edit, Delete, - Save, - Cancel, ExpandMore, ExpandLess, - Close as CloseIcon, } from "@mui/icons-material"; -import { useSnackbar } from "notistack"; -import { getRunStatus } from "../../utils/runStatus"; +import { + getRunStatus, + getRunStatusColor, + canTrainRun, + isRunActive, +} from "../../utils/runStatus"; import RunResults from "./RunResults"; -import FormSchemaWithSelectedModel from "../shared/FormSchemaWithSelectedModel"; -import FormSchemaContainer from "../shared/FormSchemaContainer"; -import OptimizationTableSelectOptimizer from "./modelSession/OptimizationTableSelectOptimizer"; -import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; -import useSchema from "../../hooks/useSchema"; -import { updateRunParameters, getRunOperationsCount } from "../../api/run"; -import RetrainConfirmDialog from "./RetrainConfirmDialog"; -import { renderParamValue } from "./ModelParamBlock"; +import RunEditDialog from "./RunEditDialog"; +import { getRunOperationsCount } from "../../api/run"; +import { useModelDownloadGate } from "./model/ComponentDownloadControl"; import { useTranslation } from "react-i18next"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; -import { checkIfHaveOptimazers } from "../../utils/schema"; /** * Card component displaying a model run with actions and details @@ -67,59 +48,51 @@ function RunCard({ existingRuns = [], onRefresh, isHighlighted = false, + forceExpanded = false, + hideChrome = false, + isEditing: controlledIsEditing = undefined, + setIsEditing: setControlledIsEditing = undefined, + deleteConfirmOpen: controlledDeleteConfirmOpen = undefined, + setDeleteConfirmOpen: setControlledDeleteConfirmOpen = undefined, }) { const theme = useTheme(); const { t } = useTranslation(["models", "common"]); - const { enqueueSnackbar } = useSnackbar(); const [resultsVisible, setResultsVisible] = useState(() => { if (run.status === 0) return false; const saved = localStorage.getItem(`run-${run.id}-results-visible`); return saved ? JSON.parse(saved) : false; }); + const isResultsVisible = forceExpanded || resultsVisible; useEffect(() => { + if (forceExpanded) return; localStorage.setItem( `run-${run.id}-results-visible`, JSON.stringify(resultsVisible), ); - }, [resultsVisible, run.id]); - const [isEditing, setIsEditing] = useState(false); - const [editedName, setEditedName] = useState(run.name || ""); - const [editedParameters, setEditedParameters] = useState( - run.parameters || {}, - ); - const [editedOptimizer, setEditedOptimizer] = useState( - run.optimizer_name || "", - ); - const [editedOptimizerParams, setEditedOptimizerParams] = useState( - run.optimizer_parameters || {}, - ); - const [editedGoalMetric, setEditedGoalMetric] = useState( - run.goal_metric || "", - ); + }, [resultsVisible, run.id, forceExpanded]); + + const isEditingControlled = controlledIsEditing !== undefined; + const [internalIsEditing, setInternalIsEditing] = useState(false); + const isEditing = isEditingControlled + ? controlledIsEditing + : internalIsEditing; + const setIsEditing = isEditingControlled + ? setControlledIsEditing + : setInternalIsEditing; + + const isDeleteConfirmControlled = controlledDeleteConfirmOpen !== undefined; + const [internalDeleteConfirmOpen, setInternalDeleteConfirmOpen] = + useState(false); + const deleteConfirmOpen = isDeleteConfirmControlled + ? controlledDeleteConfirmOpen + : internalDeleteConfirmOpen; + const setDeleteConfirmOpen = isDeleteConfirmControlled + ? setControlledDeleteConfirmOpen + : setInternalDeleteConfirmOpen; const [operationsCount, setOperationsCount] = useState(null); - const [isSaving, setIsSaving] = useState(false); - const [saveConfirmOpen, setSaveConfirmOpen] = useState(false); - const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [autoExpand, setAutoExpand] = useState(false); - const { - defaultValues: defaultOptimizerParams, - loading: optimizerSchemaLoading, - } = useSchema({ - modelName: isEditing ? editedOptimizer : null, - }); - - useEffect(() => { - if (!isEditing) { - setEditedName(run.name || ""); - setEditedParameters(run.parameters || {}); - setEditedOptimizer(run.optimizer_name || ""); - setEditedOptimizerParams(run.optimizer_parameters || {}); - setEditedGoalMetric(run.goal_metric || ""); - } - }, [run, isEditing]); - const fetchOperationsCount = useCallback(async () => { if (!run?.id) return; try { @@ -134,139 +107,13 @@ function RunCard({ fetchOperationsCount(); }, [fetchOperationsCount, explainerRefreshTrigger]); - const hasOptimizableParams = useMemo(() => { - return checkIfHaveOptimazers(editedParameters); - }, [editedParameters]); - - const handleStartEdit = () => { - setIsEditing(true); - }; - - const handleCancelEdit = () => { - setIsEditing(false); - setEditedName(run.name || ""); - setEditedParameters(run.parameters || {}); - setEditedOptimizer(run.optimizer_name || ""); - setEditedOptimizerParams(run.optimizer_parameters || {}); - setEditedGoalMetric(run.goal_metric || ""); - }; - - const doSave = async () => { - setSaveConfirmOpen(false); - setIsSaving(true); - try { - await updateRunParameters( - run.id.toString(), - editedName.trim(), - editedParameters, - editedOptimizer || "", - { ...defaultOptimizerParams, ...editedOptimizerParams }, - editedGoalMetric || "", - ); - - enqueueSnackbar( - t("models:message.runUpdatedSuccess", { runName: editedName }), - { variant: "success" }, - ); - - setIsEditing(false); - - if (onRefresh) { - await onRefresh(); - } - await fetchOperationsCount(); - } catch (error) { - console.error("Error updating run:", error); - enqueueSnackbar( - t("models:error.failedToUpdateRun", { - error: error.message || t("common:unknownError"), - }), - { variant: "error" }, - ); - } finally { - setIsSaving(false); - } - }; - - const handleSaveEdit = async () => { - if (!editedName.trim()) { - enqueueSnackbar(t("models:error.runNameEmpty"), { variant: "warning" }); - return; - } - - const nameExists = existingRuns.some( - (r) => - r.id !== run.id && - r.name && - r.name.toLowerCase() === editedName.trim().toLowerCase(), - ); - if (nameExists) { - enqueueSnackbar( - t("models:error.runNameExists", { name: editedName.trim() }), - { variant: "error" }, - ); - return; - } - - if (hasOptimizableParams) { - if (!editedOptimizer) { - enqueueSnackbar(t("models:error.selectOptimizerRequired"), { - variant: "warning", - }); - return; - } - if (!editedGoalMetric) { - enqueueSnackbar(t("models:error.selectGoalMetricRequired"), { - variant: "warning", - }); - return; - } - } - - // If operations exist, warn before saving (they will be deleted on next train) - if ( - operationsCount && - (operationsCount.explainers > 0 || operationsCount.predictions > 0) - ) { - setSaveConfirmOpen(true); - return; - } - - await doSave(); - }; - - const handleParametersChange = useCallback((values) => { - setEditedParameters(values); - }, []); - - const handleOptimizerParamsChange = useCallback((values) => { - setEditedOptimizerParams(values); - }, []); - - const handleOptimizerSelected = (optimizerName) => { - setEditedOptimizer(optimizerName); - setEditedOptimizerParams({}); - }; - const statusText = getRunStatus(run.status, t); const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; - const getStatusColor = (status) => { - switch (status) { - case 0: - return "default"; - case 1: - case 2: - return "info"; - case 3: - return "success"; - case 4: - return "error"; - default: - return "default"; - } - }; + // A download-required model must be downloaded before it can be trained. + // Track the live download state so the button reflects an inline download. + const { modelNotDownloaded } = useModelDownloadGate(model, run.model_name); useEffect(() => { if (run.status !== 1 && run.status !== 2) { @@ -274,8 +121,8 @@ function RunCard({ } }, [run.status]); - const canTrain = run.status === 0 || run.status === 3 || run.status === 4; // Not Started, Finished, Error - const isRunning = run.status === 1 || run.status === 2; // Delivered, Started + const canTrain = canTrainRun(run.status); + const isRunning = isRunActive(run.status); const getMetrics = () => { if (!run.trained_models || run.trained_models.length === 0) { @@ -299,214 +146,257 @@ function RunCard({ return ( - - - - + {!hideChrome && ( + <> + - {modelDisplayName} - - ({run.name}) - - - - - - {!isRunning && ( - - )} - {canTrain && ( - 0 || - operationsCount.predictions > 0) - ? t("models:message.retrainWillResetOperations", { - explainersCount: operationsCount.explainers, - predictionsCount: operationsCount.predictions, - }) - : "" - } - > - - - )} - {isRunning && ( - - )} - - + {run.name} + + ({modelDisplayName}) + + + - - setDeleteConfirmOpen(true)} - disabled={isRunning} - > - - - - - setResultsVisible(!resultsVisible)} - color="default" - > - {resultsVisible ? ( - - ) : ( - + + {!isRunning && ( + + )} + {canTrain && ( + 0 || + operationsCount.predictions > 0) + ? t("models:message.retrainWillResetOperations", { + explainersCount: operationsCount.explainers, + predictionsCount: operationsCount.predictions, + }) + : "" + } + > + + + + + )} + {isRunning && ( + )} - - - - - {metrics && Object.keys(metrics).length > 0 && ( - - - {t("common:metrics")} - - - {Object.entries(metrics).map(([metric, values]) => { - const avgValue = - values.reduce((sum, val) => sum + val, 0) / values.length; - return ( - - + + + setDeleteConfirmOpen(true)} + disabled={isRunning} + > + + + + {!forceExpanded && ( + + setResultsVisible(!resultsVisible)} + color="default" > - {metric.toUpperCase()} - - - {avgValue.toFixed(4)} - - - ); - })} + {resultsVisible ? ( + + ) : ( + + )} +
+ + )} + - - )} - {run.description && ( - - {run.description} - + {metrics && Object.keys(metrics).length > 0 && ( + + + {t("common:metrics")} + + + {Object.entries(metrics).map(([metric, values]) => { + const avgValue = + values.reduce((sum, val) => sum + val, 0) / values.length; + return ( + + + {metric.toUpperCase()} + + + {avgValue.toFixed(4)} + + + ); + })} + + + )} + + {run.description && ( + + {run.description} + + )} + )} - + - setSaveConfirmOpen(false)} - onConfirm={doSave} - run={run} - operationsCount={operationsCount} - /> setDeleteConfirmOpen(false)} @@ -520,138 +410,14 @@ function RunCard({ /> - - - - - {t("models:label.editRun")} - - - - - - - - - - - {t("models:message.editingParametersWarning")} - - - setEditedName(e.target.value)} - fullWidth - required - size="small" - /> - - {run.model_name && ( - - - {t("common:modelParameters")} - - - {}} - hideButtons - /> - - - )} - - {hasOptimizableParams && ( - - - - {t("models:label.hyperparameterOptimizerConfiguration")} - - - {t("models:message.parametersMarkedForOptimization")} - - - - - {t("models:label.goalMetric")} * - - - - - - - {editedOptimizer && ( - - - {t("common:optimizerParameters")} - - - - setEditedOptimizerParams(values) - } - onValuesChange={handleOptimizerParamsChange} - onCancel={() => {}} - hideButtons - /> - - - )} - - )} - - - - - - - - + onClose={() => setIsEditing(false)} + /> ); } @@ -684,6 +450,12 @@ RunCard.propTypes = { isLastRun: PropTypes.bool, existingRuns: PropTypes.array, onRefresh: PropTypes.func, + forceExpanded: PropTypes.bool, + hideChrome: PropTypes.bool, + isEditing: PropTypes.bool, + setIsEditing: PropTypes.func, + deleteConfirmOpen: PropTypes.bool, + setDeleteConfirmOpen: PropTypes.func, }; export default RunCard; diff --git a/DashAI/front/src/components/models/RunEditDialog.jsx b/DashAI/front/src/components/models/RunEditDialog.jsx new file mode 100644 index 000000000..23316a678 --- /dev/null +++ b/DashAI/front/src/components/models/RunEditDialog.jsx @@ -0,0 +1,191 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { + Box, + Typography, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, + Stepper, + Step, + StepLabel, + Alert, +} from "@mui/material"; +import { Close as CloseIcon } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import RetrainConfirmDialog from "./RetrainConfirmDialog"; +import RunEditForm from "./RunEditForm"; +import useRunEditForm from "../../hooks/useRunEditForm"; + +/** + * Editable-parameters dialog for a run — the same form used to configure it + * before training, pre-filled with its current values. Shared by RunCard's + * "Editar" button and the compact model card's quick-edit action so both + * entry points open the exact same modal. + * + * Same two-step Stepper pattern as AddModelDialog: a second "Configure + * Optimizer" step only appears once a parameter is marked for optimization. + */ +export default function RunEditDialog({ + run, + session, + existingRuns = [], + onRefresh, + open, + onClose, +}) { + const { t } = useTranslation(["models", "common"]); + const [activeStep, setActiveStep] = useState(0); + + const formProps = useRunEditForm({ + run, + session, + existingRuns, + onRefresh, + onSaved: onClose, + enabled: open, + }); + + const { + canSave, + hasOptimizableParams, + validateBasics, + operationsCount, + isSaving, + saveConfirmOpen, + setSaveConfirmOpen, + doSave, + handleSaveEdit, + } = formProps; + + // Always start on the first step when the dialog (re)opens. + useEffect(() => { + if (open) setActiveStep(0); + }, [open]); + + const steps = hasOptimizableParams + ? [t("models:label.configureModel"), t("models:label.configureOptimizer")] + : [t("models:label.configureModel")]; + const isLastStep = activeStep === steps.length - 1; + + const handleNext = () => { + if (!validateBasics()) return; + setActiveStep(1); + }; + + const handleBack = () => { + if (activeStep > 0) setActiveStep(activeStep - 1); + }; + + const handlePrimaryAction = () => { + if (isLastStep) { + handleSaveEdit(); + } else { + handleNext(); + } + }; + + return ( + <> + + + + + {t("models:label.editRun")} + + + + + + + + + + {t("models:message.editingParametersWarning")} + + + + {steps.map((label) => ( + + {label} + + ))} + + + + + + + + {activeStep > 0 && ( + + )} + + + + + setSaveConfirmOpen(false)} + onConfirm={doSave} + run={run} + operationsCount={operationsCount} + /> + + ); +} + +RunEditDialog.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + model_name: PropTypes.string, + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + session: PropTypes.shape({ + task_name: PropTypes.string, + }), + existingRuns: PropTypes.array, + onRefresh: PropTypes.func, + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/models/RunEditForm.jsx b/DashAI/front/src/components/models/RunEditForm.jsx new file mode 100644 index 000000000..7f725590f --- /dev/null +++ b/DashAI/front/src/components/models/RunEditForm.jsx @@ -0,0 +1,132 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, TextField } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import FormSchemaWithSelectedModel from "../shared/FormSchemaWithSelectedModel"; +import FormSchemaContainer from "../shared/FormSchemaContainer"; +import OptimizationTableSelectOptimizer from "./modelSession/OptimizationTableSelectOptimizer"; +import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; + +/** + * The actual "edit a run's parameters" form body, split into the same two + * steps as AddModelDialog: step 0 is the run name and model parameters, + * step 1 (only reachable when a parameter is marked for optimization) is + * the goal metric, optimizer, and its parameters. Rendered inside the + * "Edit Run" dialog (RunEditDialog), which owns the step/Stepper and the + * Back/Next/Save actions. + */ +export default function RunEditForm({ + run, + activeStep, + taskName, + editedName, + setEditedName, + editedParameters, + handleParametersChange, + editedOptimizer, + editedOptimizerParams, + setEditedOptimizerParams, + handleOptimizerParamsChange, + handleOptimizerSelected, + editedGoalMetric, + setEditedGoalMetric, +}) { + const { t } = useTranslation(["models", "common"]); + + if (activeStep === 1) { + return ( + + + {t("models:label.optimizerConfiguration")} + + + + + {t("models:label.goalMetric")} * + + + + + + + {editedOptimizer && ( + + + {t("common:optimizerParameters")} + + + setEditedOptimizerParams(values)} + onValuesChange={handleOptimizerParamsChange} + onCancel={() => {}} + hideButtons + /> + + + )} + + ); + } + + return ( + + setEditedName(e.target.value)} + fullWidth + required + size="small" + /> + + {run.model_name && ( + + + {t("common:modelParameters")} + + + {}} + hideButtons + /> + + + )} + + ); +} + +RunEditForm.propTypes = { + run: PropTypes.shape({ + model_name: PropTypes.string, + }).isRequired, + activeStep: PropTypes.number.isRequired, + taskName: PropTypes.string, + editedName: PropTypes.string.isRequired, + setEditedName: PropTypes.func.isRequired, + editedParameters: PropTypes.object.isRequired, + handleParametersChange: PropTypes.func.isRequired, + editedOptimizer: PropTypes.string.isRequired, + editedOptimizerParams: PropTypes.object.isRequired, + setEditedOptimizerParams: PropTypes.func.isRequired, + handleOptimizerParamsChange: PropTypes.func.isRequired, + handleOptimizerSelected: PropTypes.func.isRequired, + editedGoalMetric: PropTypes.string.isRequired, + setEditedGoalMetric: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/models/RunInfoSidebar.jsx b/DashAI/front/src/components/models/RunInfoSidebar.jsx new file mode 100644 index 000000000..f7807141b --- /dev/null +++ b/DashAI/front/src/components/models/RunInfoSidebar.jsx @@ -0,0 +1,154 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, Tabs, Tab } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import SideBar from "../threeSectionLayout/panelContainers/SideBar"; +import ModelConfigurationContent from "./ModelConfigurationContent"; +import SessionInfoContent from "./SessionInfoContent"; +import ParamInfoList from "./ParamInfoBox"; + +function formatCreatedDate(dateStr, locale) { + if (!dateStr) return null; + const date = new Date(dateStr); + if (Number.isNaN(date.getTime())) return null; + return date.toLocaleString(locale, { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +function formatDuration(startTime, endTime) { + if (!startTime || !endTime) return null; + const totalSeconds = Math.max( + 0, + Math.round((new Date(endTime) - new Date(startTime)) / 1000), + ); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return minutes > 0 + ? `${minutes}m ${String(seconds).padStart(2, "0")}s` + : `${seconds}s`; +} + +/** + * Right-side panel shown while inside a run's full-screen model detail + * view. Two tabs, same pattern as the notebook Explore/Convert sidebar: + * "Run" (the run's quick facts and trained configuration — what the "Run + * Information" modal used to show) and "Session" (the parent session's + * setup, same content as InfoSessionModal), both always visible inline + * instead of behind a separate button. + */ +export default function RunInfoSidebar({ + run, + model, + datasetName, + session, + datasets, + tasks, +}) { + const theme = useTheme(); + const { t, i18n } = useTranslation(["models", "common"]); + const [activeTab, setActiveTab] = useState(0); + + const modelDisplayName = model?.display_name || run.model_name; + const createdLabel = formatCreatedDate(run.created, i18n.language); + const durationLabel = formatDuration(run.start_time, run.end_time); + + const rows = [ + [t("common:id"), run.id], + [t("common:model"), modelDisplayName], + [t("common:associatedDataset"), datasetName || t("common:unknown")], + [t("common:createdAt"), createdLabel || t("common:unknown")], + [t("common:duration"), durationLabel || t("common:unknown")], + ]; + + return ( + + + + {t("models:label.configuration")} + + + + setActiveTab(newValue)} + centered + sx={{ + flexShrink: 0, + borderBottom: `1px solid ${theme.palette.ui.border}`, + }} + > + + + + + + {activeTab === 0 ? ( + + + + + + {t("common:metadata")} + + + + + ) : ( + + )} + + + ); +} + +RunInfoSidebar.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + model_name: PropTypes.string, + created: PropTypes.string, + start_time: PropTypes.string, + end_time: PropTypes.string, + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + model: PropTypes.shape({ + display_name: PropTypes.string, + schema: PropTypes.object, + }), + datasetName: PropTypes.string, + session: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + dataset_id: PropTypes.number, + task_name: PropTypes.string, + input_columns: PropTypes.array, + output_columns: PropTypes.array, + splits: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + created: PropTypes.string, + last_modified: PropTypes.string, + description: PropTypes.string, + }), + datasets: PropTypes.array, + tasks: PropTypes.array, +}; diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index 7b34cf45d..c3c7f3b25 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -1,68 +1,62 @@ -import React, { useState, useEffect, useCallback, useRef } from "react"; +import React, { useState, useEffect } from "react"; import PropTypes from "prop-types"; -import { - Box, - Typography, - Button, - Chip, - Stack, - Collapse, - Tabs, - Tab, - Grid, - IconButton, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Paper, -} from "@mui/material"; -import { renderParamValue } from "./ModelParamBlock"; -import { - ExpandMore as ExpandMoreIcon, - ExpandLess as ExpandLessIcon, - Add as AddIcon, - TrendingUp as TrendingUpIcon, - Close as CloseIcon, -} from "@mui/icons-material"; -import ExplainersCard from "../explainers/ExplainersCard"; -import PredictionCard from "./PredictionCard"; -import { LoadingButton } from "@mui/lab"; -import InlineExplainerCreator from "../explainers/InlineExplainerCreator"; -import DatasetPredictionPanel from "./DatasetPredictionPanel"; -import ManualPredictionPanel from "./ManualPredictionPanel"; +import { Box, Collapse, Divider } from "@mui/material"; +import { useParams } from "react-router-dom"; + import LiveMetricsChart from "./LiveMetricsChart"; import HyperparameterPlots from "./HyperparameterPlots"; -import { getExplainers } from "../../api/explainer"; -import { getPredictions } from "../../api/predict"; -import { getModelSessionById } from "../../api/modelSession"; -import { getDatasetSample } from "../../api/datasets"; import { checkHowManyOptimazers } from "../../utils/schema"; -import { useTranslation } from "react-i18next"; -import TimestampWrapper from "../shared/TimestampWrapper"; -import { TIMESTAMP_KEYS } from "../../constants/timestamp"; - +import { isRunActive } from "../../utils/runStatus"; +import { useModels } from "./ModelsContext"; +import useRunResultsData from "./runResults/useRunResultsData"; +import ResultsTabsHeader from "./runResults/ResultsTabsHeader"; +import ExplainerResultsTab from "./runResults/ExplainerResultsTab"; +import PredictionResultsTab from "./runResults/PredictionResultsTab"; + +/** + * Shows a run's results as two tab groups (metrics: live/hyperparameters, + * operations: explainability/predictions). The data layer lives in + * useRunResultsData; each tab body is its own component. This component wires + * them together and owns only the view level state: which tab is active, + * whether the (inline) panel is expanded, the scroll container the explainer + * list virtualizes against, and the dataset-prediction dialog visibility. + */ export default function RunResults({ run, - model, session, onRefresh, explainerRefreshTrigger, resultsVisible: controlledVisible = undefined, setResultsVisible: setControlledVisible = undefined, autoExpand = false, + fillHeight = false, }) { const isControlled = controlledVisible !== undefined; - const [globalExplainers, setGlobalExplainers] = useState([]); - const [localExplainers, setLocalExplainers] = useState([]); - const [predictions, setPredictions] = useState([]); + const { + globalExplainers, + localExplainers, + predictions, + activeExplainers, + explainerFilter, + setExplainerFilter, + newExplainerKey, + setNewExplainerKey, + highlightedExplainerKey, + setHighlightedExplainerKey, + explainerDisplayNames, + cardHeightsRef, + getCacheEntry, + updateCacheEntry, + predictionDisplayNumbers, + outputColumn, + trainingDatasetSample, + fetchOperations, + handlePredictionCreated, + handleExplainerDeleted, + handlePredictionDeleted, + } = useRunResultsData({ run, session, onRefresh, explainerRefreshTrigger }); + const [internalVisible, setInternalVisible] = useState(() => { if (run.status === 0) return false; const saved = localStorage.getItem(`run-${run.id}-results-visible`); @@ -73,112 +67,18 @@ export default function RunResults({ ? setControlledVisible : setInternalVisible; - const [paramsExpanded, setParamsExpanded] = useState(false); - - const [activeTab, setActiveTab] = useState(() => { - const saved = localStorage.getItem(`run-${run.id}-active-tab`); - if (saved !== null) { - const savedTab = JSON.parse(saved); - // Tabs 1+ (Explainability, Predictions, Hyperparameters) require a finished run - if (savedTab > 0 && run.status !== 3) return 0; - return savedTab; - } - return 0; - }); - - const [trainingDatasetSample, setTrainingDatasetSample] = useState(null); - const [outputColumn, setOutputColumn] = useState(null); + // Always land on Live Metrics (tab 0) when a run's results are shown - + // no per-run "last tab" persistence, so opening a model card is predictable. + const [activeTab, setActiveTab] = useState(0); - const [globalCreatorOpen, setGlobalCreatorOpen] = useState(false); - const [localCreatorOpen, setLocalCreatorOpen] = useState(false); - const [globalExpanded, setGlobalExpanded] = useState(true); - const [localExpanded, setLocalExpanded] = useState(true); - const [datasetExpanded, setDatasetExpanded] = useState(true); - const [manualExpanded, setManualExpanded] = useState(true); + // Detail view scroll container, kept in state so the explainer list receives + // it as its virtualization scroll parent. + const [explainerScrollParent, setExplainerScrollParent] = useState(null); const [showDatasetPanel, setShowDatasetPanel] = useState(false); - const datasetRunRef = useRef(null); - const [datasetRunState, setDatasetRunState] = useState({ - canRun: false, - isSubmitting: false, - }); - const [showManualPanel, setShowManualPanel] = useState(false); - const manualSaveRef = useRef(null); - const [manualSaveState, setManualSaveState] = useState({ - canSave: false, - isSaving: false, - }); - - // Map a parameter key to its display name using the matching model's schema - // (the model comes from the right side bar list, so no extra backend fetch). - const paramProperties = model?.schema?.properties ?? {}; - const getParamLabel = (key) => paramProperties[key]?.title ?? key; const optimizables = checkHowManyOptimazers({ params: run.parameters }); const isFinished = run.status === 3; - const isRunning = run.status === 1 || run.status === 2; - const { t } = useTranslation(["models", "common"]); - - const runId = run.id; - const fetchOperations = useCallback(async () => { - if (!runId) return; - - try { - const [globalExpls, localExpls, preds] = await Promise.all([ - getExplainers(runId, "global").catch(() => []), - getExplainers(runId, "local").catch(() => []), - getPredictions(runId).catch(() => []), - ]); - - setGlobalExplainers(globalExpls); - setLocalExplainers(localExpls); - setPredictions(preds); - } catch (error) { - console.error("Error fetching operations:", error); - } - }, [runId]); - - useEffect(() => { - fetchOperations(); - }, [fetchOperations, explainerRefreshTrigger]); - - useEffect(() => { - const sessionId = run.model_session_id || session?.id; - if (!sessionId) return; - let cancelled = false; - getModelSessionById(sessionId) - .then((sessionData) => { - if (cancelled) return null; - setOutputColumn(sessionData.output_columns?.[0] ?? null); - return getDatasetSample(sessionData.dataset_id); - }) - .then((sample) => { - if (!cancelled && sample) setTrainingDatasetSample(sample); - }) - .catch(() => {}); - return () => { - cancelled = true; - }; - }, [run.model_session_id, session?.id]); - - // Refetch when run parameters change (after editing) - useEffect(() => { - fetchOperations(); - }, [ - run.parameters, - run.optimizer_parameters, - run.goal_metric, - fetchOperations, - ]); - - const hasRunningExplainers = - globalExplainers.some((e) => e.status === 1 || e.status === 2) || - localExplainers.some((e) => e.status === 1 || e.status === 2); - - useEffect(() => { - if (!hasRunningExplainers) return; - const interval = setInterval(fetchOperations, 3000); - return () => clearInterval(interval); - }, [hasRunningExplainers, fetchOperations]); + const isRunning = isRunActive(run.status); useEffect(() => { const handleOpenDialog = (event) => { @@ -208,695 +108,113 @@ export default function RunResults({ ); }, [resultsVisible, run.id, isControlled]); + // Expose the active tab while this run is shown full screen, so the right + // sidebar can swap its content (e.g. list explainers on the explainers tab). + const params = useParams(); + const modelsContext = useModels(); + const setRunDetailTab = modelsContext?.setRunDetailTab; + const isDetailView = String(params.runId ?? "") === String(run.id); useEffect(() => { - localStorage.setItem(`run-${run.id}-active-tab`, JSON.stringify(activeTab)); - }, [activeTab, run.id]); - - const handleExplainerCreated = () => { - fetchOperations(); - if (onRefresh) onRefresh(); - }; - - const handlePredictionCreated = (prediction) => { - if (prediction) { - setPredictions((prev) => { - const index = prev.findIndex((p) => p.id === prediction.id); - if (index === -1) { - return [prediction, ...prev]; - } - - const updated = [...prev]; - updated[index] = prediction; - return updated; - }); - } else { - fetchOperations(); - } - - if (onRefresh) onRefresh(); - }; - - const handleExplainerDeleted = () => { - fetchOperations(); - if (onRefresh) onRefresh(); - }; + if (!isDetailView || !setRunDetailTab) return; + setRunDetailTab(activeTab); + return () => setRunDetailTab(null); + }, [isDetailView, activeTab, setRunDetailTab]); + + const tabsHeader = ( + + ); - const handlePredictionDeleted = () => { - fetchOperations(); - if (onRefresh) onRefresh(); - }; + const tabContent = ( + <> + {activeTab === 0 && ( + + + + )} - const totalOperations = - globalExplainers.length + localExplainers.length + predictions.length; + {activeTab === 1 && isFinished && ( + + )} - const hasParams = - (run.parameters && Object.keys(run.parameters).length > 0) || - (run.optimizer_name && run.goal_metric); + {activeTab === 2 && isFinished && ( + + )} - return ( - - {hasParams && ( - - - - - {run.parameters && Object.keys(run.parameters).length > 0 && ( - - - - - - {t("common:parameter")} - {t("common:value")} - - - - {Object.entries(run.parameters).map(([key, value]) => ( - - {getParamLabel(key)} - {renderParamValue(value)} - - ))} - -
-
-
- )} - {run.optimizer_name && run.goal_metric && ( - - - {t("common:optimizer")}: {run.optimizer_name} - - {run.optimizer_parameters && - Object.keys(run.optimizer_parameters).length > 0 && ( - - - - - {t("common:parameter")} - {t("common:value")} - - - - {Object.entries(run.optimizer_parameters).map( - ([key, value]) => ( - - {key} - - {renderParamValue(value)} - - - ), - )} - -
-
- )} - - {t("models:label.goalMetric")}:{" "} - {run.goal_metric} - -
- )} -
-
+ {activeTab === 3 && isFinished && optimizables > 0 && ( + + )} + + ); - - - setActiveTab(newValue)} - aria-label="Results tabs" - > - - - {t("models:label.explainability")} - {isFinished && ( - - )} - - } - disabled={!isFinished} - /> - - {t("models:label.predictions")} - {isFinished && ( - - )} -
- } - disabled={!isFinished} - /> - - + // Detail view: fixed header and tabs, only the content scrolls. Card list + // view keeps the collapsible, content sized layout. + if (fillHeight) { + return ( + + + {tabsHeader} + + + {tabContent} + + + ); + } - {activeTab === 0 && ( - - - - )} - - {activeTab === 1 && isFinished && ( - - - - - - - - - - - - - - - setGlobalCreatorOpen(false)} - /> - setLocalCreatorOpen(false)} - /> - - - - - - - {t("models:label.globalExplainers")} - - - - setGlobalExpanded((prev) => !prev)} - > - {globalExpanded ? ( - - ) : ( - - )} - - - - {globalExplainers.length === 0 ? ( - - {t("models:label.noGlobalExplainersYet")} - - ) : ( - - {globalExplainers.map((explainer) => ( - - ))} - - )} - - - - - - - - {t("models:label.localExplainers")} - - - - setLocalExpanded((prev) => !prev)} - > - {localExpanded ? ( - - ) : ( - - )} - - - - {localExplainers.length === 0 ? ( - - {t("models:label.noLocalExplainersYet")} - - ) : ( - - {localExplainers.map((explainer) => ( - - ))} - - )} - - - - - )} - - {activeTab === 2 && isFinished && ( - - - - - - - - - - - setShowDatasetPanel(false)} - maxWidth="md" - fullWidth - PaperProps={{ sx: { minHeight: "500px" } }} - > - - - - {t("models:button.newDatasetPrediction")} - - setShowDatasetPanel(false)} - sx={{ color: "text.secondary" }} - > - - - - - - { - handlePredictionCreated(prediction); - setShowDatasetPanel(false); - }} - onClose={() => setShowDatasetPanel(false)} - runRef={datasetRunRef} - onStateChange={setDatasetRunState} - /> - - - - datasetRunRef.current?.()} - > - {t("prediction:button.runPrediction")} - - - - - setShowManualPanel(false)} - maxWidth="lg" - fullWidth - PaperProps={{ sx: { minHeight: "500px" } }} - > - - - - {t("models:button.newManualPrediction")} - - setShowManualPanel(false)} - sx={{ color: "text.secondary" }} - > - - - - - - { - handlePredictionCreated(prediction); - setShowManualPanel(false); - }} - onClose={() => setShowManualPanel(false)} - saveRef={manualSaveRef} - onStateChange={setManualSaveState} - /> - - - - manualSaveRef.current?.()} - > - {t("prediction:button.saveResults")} - - - - - - - - - - {t("models:label.datasetPredictions")} - - p.dataset_id).length} - size="small" - color="primary" - /> - - setDatasetExpanded((prev) => !prev)} - > - {datasetExpanded ? ( - - ) : ( - - )} - - - - {predictions.filter((p) => p.dataset_id).length === 0 ? ( - - {t("models:label.noDatasetPredictionsYet")} - - ) : ( - - {predictions - .filter((p) => p.dataset_id) - .map((prediction) => ( - - ))} - - )} - - - - - - - - {t("models:label.manualPredictions")} - - !p.dataset_id).length} - size="small" - color="primary" - /> - - setManualExpanded((prev) => !prev)} - > - {manualExpanded ? ( - - ) : ( - - )} - - - - {predictions.filter((p) => !p.dataset_id).length === 0 ? ( - - {t("models:label.noManualPredictionsYet")} - - ) : ( - - {predictions - .filter((p) => !p.dataset_id) - .map((prediction) => ( - - ))} - - )} - - - - - )} - - {activeTab === 3 && isFinished && optimizables > 0 && ( - - - - )} + return ( + + + {tabsHeader} + + {tabContent} ); @@ -913,11 +231,6 @@ RunResults.propTypes = { model_session_id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), test_metrics: PropTypes.object, }).isRequired, - model: PropTypes.shape({ - name: PropTypes.string, - display_name: PropTypes.string, - schema: PropTypes.object, - }), session: PropTypes.shape({ id: PropTypes.number, name: PropTypes.string, @@ -928,4 +241,5 @@ RunResults.propTypes = { resultsVisible: PropTypes.bool, setResultsVisible: PropTypes.func, autoExpand: PropTypes.bool, + fillHeight: PropTypes.bool, }; diff --git a/DashAI/front/src/components/models/SessionInfoContent.jsx b/DashAI/front/src/components/models/SessionInfoContent.jsx new file mode 100644 index 000000000..a4b00e71b --- /dev/null +++ b/DashAI/front/src/components/models/SessionInfoContent.jsx @@ -0,0 +1,151 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, Chip } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { formatDate } from "../../utils"; +import ParamInfoList from "./ParamInfoBox"; + +const SPLIT_TYPE_LABEL_KEYS = { + random: "experiments:label.random", + manual: "experiments:label.manual", + predefined: "experiments:label.predefined", +}; + +/** + * Shared body for a session's info: task, description, metadata (id, + * dataset, dates) and the configuration it was created with (input/output + * columns, split setup). Used both by InfoSessionModal (a dialog) and the + * model detail view's right sidebar (RunInfoSidebar's "Session" tab). + */ +export default function SessionInfoContent({ + session, + datasets = [], + tasks = [], +}) { + const { t } = useTranslation(["common", "experiments", "models"]); + + if (!session) return null; + + const getDatasetName = () => { + if (!session.dataset_id || !datasets.length) return t("common:unknown"); + const dataset = datasets.find((d) => d.id === session.dataset_id); + return dataset ? dataset.name : t("common:datasetNotFound"); + }; + + const getTaskDisplayName = () => { + if (!session.task_name) return t("common:unknown"); + const task = tasks.find((tk) => tk.name === session.task_name); + return ( + task?.metadata?.display_name || + session.task_name + .replace("Task", "") + .replace(/([A-Z])/g, " $1") + .trim() + ); + }; + + const metadataRows = [ + [t("common:id"), session.id], + [t("common:associatedDataset"), getDatasetName()], + [t("common:createdAt"), formatDate(session.created)], + [t("common:lastModified"), formatDate(session.last_modified)], + ]; + + let splits = null; + try { + splits = + typeof session.splits === "string" + ? JSON.parse(session.splits) + : session.splits; + } catch { + splits = null; + } + + const yesNo = (value) => t(value ? "common:yes" : "common:no"); + + const configRows = [ + [t("models:label.inputColumns"), (session.input_columns || []).join(", ")], + [ + t("models:label.outputColumns"), + (session.output_columns || []).join(", "), + ], + ]; + + if (splits?.splitType) { + configRows.push([ + t("experiments:label.splitType"), + t(SPLIT_TYPE_LABEL_KEYS[splits.splitType] || splits.splitType), + ]); + + if (splits.splitType === "random") { + configRows.push( + [t("common:train"), splits.train], + [t("common:validation"), splits.validation], + [t("common:test"), splits.test], + [t("experiments:label.shuffle"), yesNo(splits.shuffle)], + [t("experiments:label.stratify"), yesNo(splits.stratify)], + [t("experiments:label.seed"), splits.seed], + ); + } else { + configRows.push( + [t("common:train"), (splits.train || []).length], + [t("common:validation"), (splits.validation || []).length], + [t("common:test"), (splits.test || []).length], + ); + } + } + + return ( + + + + {session.description && session.description.trim() && ( + + + {t("common:description")} + + + {session.description} + + + )} + + + + + {t("models:label.sessionConfiguration")} + + + + + + + {t("common:metadata")} + + + + + ); +} + +SessionInfoContent.propTypes = { + session: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + dataset_id: PropTypes.number, + task_name: PropTypes.string, + input_columns: PropTypes.array, + output_columns: PropTypes.array, + splits: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + created: PropTypes.string, + last_modified: PropTypes.string, + description: PropTypes.string, + }), + datasets: PropTypes.array, + tasks: PropTypes.array, +}; diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 2fd1e0da3..16132c1f1 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -1,31 +1,19 @@ import React, { useState, useEffect } from "react"; -import { - Box, - Typography, - Stack, - Accordion, - AccordionSummary, - AccordionDetails, - Divider, - Button, - ButtonGroup, - ToggleButtonGroup, - ToggleButton, - Tooltip, -} from "@mui/material"; +import { Box, Typography, Divider, Button, ToggleButton } from "@mui/material"; import { useTheme } from "@mui/material/styles"; -import { - PlayArrow, - TableChart, - BarChart, - ExpandMore, -} from "@mui/icons-material"; +import { useParams, useNavigate } from "react-router-dom"; +import { PlayArrow } from "@mui/icons-material"; import ModelComparisonTable from "./ModelComparisonTable"; -import RunCard from "./RunCard"; +import ModelDetailView from "./ModelDetailView"; +import ModelCardCompact from "./ModelCardCompact"; import { getComponents } from "../../api/component"; +import { getComponentDownloadState } from "./model/ComponentDownloadControl"; import ResultsGraphs from "../../pages/results/components/ResultsGraphs"; import RetrainConfirmDialog from "./RetrainConfirmDialog"; +import ModelsBreadcrumbs from "./ModelsBreadcrumbs"; +import PillToggleButtonGroup from "../shared/PillToggleButtonGroup"; import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; import { useModels } from "./ModelsContext"; import { useTourContext } from "../tour/TourProvider"; @@ -34,19 +22,17 @@ export default function SessionVisualization() { const [models, setModels] = useState([]); const [selectedRunId, setSelectedRunId] = useState(null); const [highlightedRunId, setHighlightedRunId] = useState(null); - const [tableHeight, setTableHeight] = useState(280); - const [showTable, setShowTable] = useState(true); - const [previousTableHeight, setPreviousTableHeight] = useState(280); - const [metricSplit, setMetricSplit] = useState("test"); - const [tableCollapsed, setTableCollapsed] = useState(false); - const [explainerRefreshTrigger, setExplainerRefreshTrigger] = useState(0); - const isResizing = React.useRef(false); + const [metricSplit, setMetricSplit] = useState("train"); const { t } = useTranslation(["models", "common"]); + const { enqueueSnackbar } = useSnackbar(); const sessionTourContext = useTourContext(); + const params = useParams(); + const navigate = useNavigate(); const { selectedSession: session, runs, + datasets, onTrainRun: onTrain, onDeleteRun, fetchRuns, @@ -58,15 +44,30 @@ export default function SessionVisualization() { lastAddedRunId, clearLastAddedRunId, selectModel, + openExplainerCreator, + explainerRefreshTrigger, + triggerExplainerRefresh, } = useModels(); const theme = useTheme(); const [isDragOver, setIsDragOver] = useState(false); const [isDragging, setIsDragging] = useState(false); + // This component stays mounted across session navigations (same route, + // different :sessionId), so metricSplit would otherwise carry over from + // whatever split was last viewed in a previous session. Reset to the + // default landing split every time the session actually changes. + useEffect(() => { + setMetricSplit("train"); + }, [session?.id]); + useEffect(() => { const onStart = (e) => { - if (e.dataTransfer.types.includes("application/x-dashai-model")) { + const types = e.dataTransfer.types; + if ( + types.includes("application/x-dashai-model") || + types.includes("application/x-dashai-explainer") + ) { setIsDragging(true); } }; @@ -82,22 +83,6 @@ export default function SessionVisualization() { }; }, []); - // Auto-expand when switching to graphs - const handleToggleView = React.useCallback( - (isTable) => { - if (!isTable && showTable) { - // Switching from Table to Graphs - setPreviousTableHeight(tableHeight); - setTableHeight(Math.max(tableHeight, 600)); - } else if (isTable && !showTable) { - // Switching from Graphs to Table - setTableHeight(previousTableHeight); - } - setShowTable(isTable); - }, - [showTable, tableHeight, previousTableHeight], - ); - const fetchModels = React.useCallback(async () => { try { const response = await getComponents({ selectTypes: ["Model"] }); @@ -111,22 +96,6 @@ export default function SessionVisualization() { fetchModels(); }, [fetchModels]); - useEffect(() => { - const handleGraphsButtonClick = (e) => { - const graphsButton = e.target.closest('[data-tour="graphs-button"]'); - if (graphsButton && sessionTourContext?.stepIndex === 7) { - setTimeout(() => { - sessionTourContext.nextStep(); - }, 500); - } - }; - - document.addEventListener("click", handleGraphsButtonClick, true); - return () => { - document.removeEventListener("click", handleGraphsButtonClick, true); - }; - }, [sessionTourContext]); - // Check if tour should start from previous tutorial useEffect(() => { const shouldStartTour = sessionStorage.getItem("startModelsSessionTour"); @@ -138,7 +107,7 @@ export default function SessionVisualization() { } }, [sessionTourContext]); - // Scroll to and highlight a newly added run card + // Scroll to a newly added run card and mark it to be highlighted useEffect(() => { if (!lastAddedRunId) return; const scrollTimer = setTimeout(() => { @@ -149,47 +118,73 @@ export default function SessionVisualization() { setHighlightedRunId(lastAddedRunId); clearLastAddedRunId(); }, 100); - const clearTimer = setTimeout(() => setHighlightedRunId(null), 4100); - return () => { - clearTimeout(scrollTimer); - clearTimeout(clearTimer); - }; + return () => clearTimeout(scrollTimer); }, [lastAddedRunId]); + // Clear the highlight a few seconds after it was set. Kept in its own + // effect (keyed on highlightedRunId, not lastAddedRunId) so it isn't + // cancelled by clearLastAddedRunId() re-triggering the effect above. + useEffect(() => { + if (!highlightedRunId) return; + const clearTimer = setTimeout(() => setHighlightedRunId(null), 1000); + return () => clearTimeout(clearTimer); + }, [highlightedRunId]); + const handleRowClick = React.useCallback((runId) => { setSelectedRunId(runId); const element = document.getElementById(`run-card-${runId}`); if (element) { element.scrollIntoView({ behavior: "smooth", block: "center" }); } + setTimeout(() => setSelectedRunId(null), 2000); }, []); - const handleViewDetails = React.useCallback((run) => { - if (!run?.id) return; - setSelectedRunId(run.id); - const element = document.getElementById(`run-card-${run.id}`); - if (element) { - element.scrollIntoView({ behavior: "smooth", block: "center" }); - } - }, []); + const handleViewDetails = React.useCallback( + (run) => { + if (!run?.id) return; + navigate(`/app/models/sessions/${session.id}/model/${run.id}`); + }, + [navigate, session?.id], + ); const sortedRuns = React.useMemo( () => [...runs].sort((a, b) => new Date(a.created) - new Date(b.created)), [runs], ); - // Check which metrics are available - const hasTrainMetrics = runs.some( - (run) => run.train_metrics && Object.keys(run.train_metrics).length > 0, - ); - const hasValidationMetrics = runs.some( - (run) => - run.validation_metrics && Object.keys(run.validation_metrics).length > 0, + const activeRun = React.useMemo( + () => + params.runId ? runs.find((r) => String(r.id) === params.runId) : null, + [runs, params.runId], ); - const hasTestMetrics = runs.some( - (run) => run.test_metrics && Object.keys(run.test_metrics).length > 0, + + const datasetName = React.useMemo( + () => datasets.find((d) => d.id === session?.dataset_id)?.name, + [datasets, session?.dataset_id], ); + // Check which metrics are available. This re-scan only needs to happen + // when `runs` itself changes, not on every render (e.g. drag state, tour + // steps, or the highlight timer toggling elsewhere in this component). + const { hasTrainMetrics, hasValidationMetrics, hasTestMetrics } = + React.useMemo( + () => ({ + hasTrainMetrics: runs.some( + (run) => + run.train_metrics && Object.keys(run.train_metrics).length > 0, + ), + hasValidationMetrics: runs.some( + (run) => + run.validation_metrics && + Object.keys(run.validation_metrics).length > 0, + ), + hasTestMetrics: runs.some( + (run) => run.test_metrics && Object.keys(run.test_metrics).length > 0, + ), + }), + [runs], + ); + const handleTrainWithTour = (run) => { if (onTrain) onTrain(run); if (sessionTourContext?.run && sessionTourContext?.stepIndex === 5) { @@ -199,37 +194,43 @@ export default function SessionVisualization() { } }; - const handleMouseMove = React.useCallback((e) => { - if (isResizing.current) { - const details = document.querySelector("[data-accordion-details]"); - if (details) { - const detailsRect = details.getBoundingClientRect(); - const newHeight = e.clientY - detailsRect.top; - const minHeight = 150; - const maxHeight = window.innerHeight * 0.7; - const clampedHeight = Math.max( - minHeight, - Math.min(maxHeight, newHeight), - ); - setTableHeight(clampedHeight); - } + // If the run being deleted is the one currently open in the detail view, + // navigate back to the session overview instead of leaving the user on a + // "run not found" screen for a run that no longer exists. + const handleDeleteRun = async (run) => { + await onDeleteRun(run); + if (params.runId && String(run.id) === params.runId) { + navigate(`/app/models/sessions/${session.id}`); } - }, []); + }; - const handleMouseUp = React.useCallback(() => { - isResizing.current = false; - document.body.style.cursor = "default"; - document.body.style.userSelect = "auto"; - }, []); + // True when a run's model is ready to train: it either needs no download or + // its download is present and not in progress (live state overrides the + // possibly stale fetched flag). + const isRunModelReady = React.useCallback( + (run) => { + const model = models.find((m) => m.name === run.model_name); + if (!model?.metadata?.requires_download) return true; + const cached = getComponentDownloadState(run.model_name); + const downloaded = cached?.downloaded ?? Boolean(model.downloaded); + const downloading = Boolean(cached?.downloading); + return downloaded && !downloading; + }, + [models], + ); - React.useEffect(() => { - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); - return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [handleMouseMove, handleMouseUp]); + // Train every not-started run whose model is downloaded, skipping (and warning + // about) any whose model still needs downloading. + const handleRunAll = () => { + const notStarted = runs.filter((r) => r.status === 0); + const ready = notStarted.filter(isRunModelReady); + ready.forEach((run) => onTrain(run)); + if (ready.length < notStarted.length) { + enqueueSnackbar(t("models:message.skippedUndownloadedRuns"), { + variant: "warning", + }); + } + }; if (!session) { return ( @@ -261,14 +262,20 @@ export default function SessionVisualization() { data-session-viz onDragOver={(e) => { if (e.dataTransfer.types.includes("Files")) e.preventDefault(); - if (!e.dataTransfer.types.includes("application/x-dashai-model")) + if ( + !e.dataTransfer.types.includes("application/x-dashai-model") && + !e.dataTransfer.types.includes("application/x-dashai-explainer") + ) return; e.preventDefault(); e.dataTransfer.dropEffect = "copy"; }} onDragEnter={(e) => { if (e.dataTransfer.types.includes("Files")) e.preventDefault(); - if (!e.dataTransfer.types.includes("application/x-dashai-model")) + if ( + !e.dataTransfer.types.includes("application/x-dashai-model") && + !e.dataTransfer.types.includes("application/x-dashai-explainer") + ) return; e.preventDefault(); setIsDragOver(true); @@ -280,13 +287,24 @@ export default function SessionVisualization() { } }} onDrop={(e) => { + const types = e.dataTransfer.types; + const isModel = types.includes("application/x-dashai-model"); + const isExplainer = types.includes("application/x-dashai-explainer"); + if (!isModel && !isExplainer) return; e.preventDefault(); setIsDragOver(false); try { - const model = JSON.parse( - e.dataTransfer.getData("application/x-dashai-model"), - ); - if (model?.name) selectModel(model); + if (isExplainer) { + const explainer = JSON.parse( + e.dataTransfer.getData("application/x-dashai-explainer"), + ); + if (explainer?.name) openExplainerCreator(explainer); + } else { + const model = JSON.parse( + e.dataTransfer.getData("application/x-dashai-model"), + ); + if (model?.name) selectModel(model); + } } catch { // ignore invalid drops } @@ -295,7 +313,7 @@ export default function SessionVisualization() { display: "flex", flexDirection: "column", height: "100%", - overflow: "hidden", + overflow: "auto", position: "relative", outline: isDragOver ? `2px dashed ${theme.palette.primary.main}` @@ -336,259 +354,249 @@ export default function SessionVisualization() {
)} - {/* Sticky Comparison Table */} - setTableCollapsed((v) => !v)} - disableGutters - elevation={1} - sx={{ - flexShrink: 0, - borderBottom: "1px solid", - borderColor: "divider", - borderRadius: "4px", - "&:before": { display: "none" }, - }} - > - - - - } - sx={{ - alignItems: "flex-start", - "& .MuiAccordionSummary-content": { my: "8px", mr: 1 }, - "& .MuiAccordionSummary-expandIconWrapper": { mt: "10px" }, - }} - > - - - {t("models:label.modelComparison")} - + {/* Model detail: full-screen view for a single run */} + {params.runId ? ( + activeRun ? ( + + ) : ( + + + + {t("models:label.runNotFound")} + + + + ) + ) : ( + <> + {/* Session header: breadcrumb, title, quick stats */} + + + e.stopPropagation()} > - {/* Metric Split Selector — controls both table and graph views */} - {(hasTrainMetrics || - hasValidationMetrics || - hasTestMetrics) && ( - { - if (newValue !== null) setMetricSplit(newValue); - }} - size="small" - > - {hasTrainMetrics && ( - - {t("common:train")} - - )} - {hasValidationMetrics && ( - - {t("common:validation")} - - )} - {hasTestMetrics && ( - - {t("common:test")} - - )} - - )} - - {/* Toggle between Table and Graphs */} - - - - + + + {session.name} + + + {t("models:label.modelCount", { + count: sortedRuns.length, + })} + {datasetName && ` | ${t("common:dataset")} ${datasetName}`} + + - {/* Run All Button */} - {runs.length > 0 && runs.some((r) => r.status === 0) && ( + {runs.length > 0 && ( )} - - - {runs.length === 0 ? ( + {/* Compact model cards: quick access to each model */} + + {runs.length === 0 ? ( + + + {t("models:label.noRunsYet")} + + + ) : ( + + {sortedRuns.map((run, index) => ( + + + navigate( + `/app/models/sessions/${session.id}/model/${run.id}`, + ) + } + isHighlighted={highlightedRunId === run.id} + /> + + ))} + + )} + + + + + {/* Comparison analysis area: table/graphs across all models */} + - - {t("models:label.noRunsYet")} + + {t("models:label.modelComparison")} + + {/* Metric Split Selector: controls both table and graph views */} + {(hasTrainMetrics || + hasValidationMetrics || + hasTestMetrics) && ( + { + if (newValue !== null) setMetricSplit(newValue); + }} + > + {hasTrainMetrics && ( + + {t("common:train")} + + )} + {hasValidationMetrics && ( + + {t("common:validation")} + + )} + {hasTestMetrics && ( + + {t("common:test")} + + )} + + )} + - ) : ( - - {showTable ? ( + + {runs.length === 0 ? ( + + + {t("models:label.noRunsYet")} + + + ) : ( + <> - ) : ( + + + {t("common:graphs")} + - )} - - )} - - {/* Resize Handle */} - { - isResizing.current = true; - document.body.style.cursor = "row-resize"; - document.body.style.userSelect = "none"; - }} - sx={{ - position: "absolute", - bottom: 0, - left: 0, - right: 0, - height: "5px", - cursor: "row-resize", - bgcolor: "transparent", - transition: "background-color 0.2s ease", - "&:hover": { bgcolor: "primary.main" }, - zIndex: 10, - }} - /> - - - - - - {/* Scrollable Run Cards */} - - {runs.length === 0 ? ( - - - {t("models:label.noRunsYet")} - + + )} - ) : ( - - {sortedRuns.map((run, index) => ( - - - setExplainerRefreshTrigger((prev) => prev + 1) - } - isLastRun={index === sortedRuns.length - 1} - existingRuns={runs} - onRefresh={fetchRuns} - isHighlighted={highlightedRunId === run.id} - /> - - ))} - - )} - + + )}
{ + if (bytes == null) return ""; + const mb = bytes / 1024 / 1024; + if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`; + return `${Math.round(mb)} MB`; +}; + +// Download state is a global, per-component fact (a component's artifacts are +// either on disk or not). The same component can be rendered by several +// controls at once (e.g. the same model selected at multiple nesting levels). +// This module-level pub/sub keeps every mounted control for a given component +// name in sync, and the cache lets a freshly mounted control pick up the +// latest known state instead of the (possibly stale) prop. +const downloadListeners = new Map(); // name -> Set<(state) => void> +const downloadStateCache = new Map(); // name -> { downloading, downloaded } +const anyChangeListeners = new Set(); // (name, state) => void +const activePollers = new Map(); // name -> poller id + +const subscribeDownloadState = (name, listener) => { + let listeners = downloadListeners.get(name); + if (!listeners) { + listeners = new Set(); + downloadListeners.set(name, listeners); + } + listeners.add(listener); + return () => { + listeners.delete(listener); + }; +}; + +// Read the last known download state for a component name, or undefined if it +// has not been tracked this session. Lets non-hook call sites (e.g. a bulk +// "run all" handler) consult the live state without subscribing. +export const getComponentDownloadState = (name) => downloadStateCache.get(name); + +// Subscribe to every download/delete regardless of component name. Lets a +// container (e.g. a config dialog) re-check which nested components still need +// downloading after an inline control finishes. +export const subscribeAnyDownloadState = (listener) => { + anyChangeListeners.add(listener); + return () => { + anyChangeListeners.delete(listener); + }; +}; + +const broadcastDownloadState = (name, state) => { + downloadStateCache.set(name, { ...downloadStateCache.get(name), ...state }); + const listeners = downloadListeners.get(name); + if (listeners) listeners.forEach((listener) => listener(state)); + anyChangeListeners.forEach((listener) => listener(name, state)); +}; + +export const stopComponentDownloadPolling = (componentName) => { + const pollerId = activePollers.get(componentName); + if (pollerId != null) { + stopJobPolling(pollerId); + activePollers.delete(componentName); + } +}; + +export const startComponentDownload = async ({ + component, + enqueueSnackbar, + t, + onStatusChange, +}) => { + broadcastDownloadState(component.name, { downloading: true }); + try { + const { id } = await downloadComponent(component.name); + activePollers.set(component.name, id); + startJobPolling( + id, + async () => { + activePollers.delete(component.name); + const status = await getComponentDownloadStatus(component.name); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: status.downloaded, + }); + if (onStatusChange) onStatusChange(status.downloaded); + enqueueSnackbar(t("common:componentDownload.done"), { + variant: "success", + }); + }, + () => { + activePollers.delete(component.name); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: false, + }); + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + }, + ); + } catch (e) { + broadcastDownloadState(component.name, { + downloading: false, + downloaded: false, + }); + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } +}; + +// Subscribe a component to the shared download state. Returns the live +// { downloaded, downloading } flags, kept in sync across every mounted control +// for the same component name. +export const useComponentDownloadState = (component) => { + const cached = downloadStateCache.get(component.name); + const [downloaded, setDownloaded] = useState( + cached?.downloaded ?? Boolean(component.downloaded), + ); + const [downloading, setDownloading] = useState(cached?.downloading ?? false); + + useEffect(() => { + const known = downloadStateCache.get(component.name); + setDownloaded(known?.downloaded ?? Boolean(component.downloaded)); + setDownloading(known?.downloading ?? false); + }, [component.name, component.downloaded]); + + useEffect(() => { + return subscribeDownloadState(component.name, (state) => { + if (state.downloading !== undefined) setDownloading(state.downloading); + if (state.downloaded !== undefined) setDownloaded(state.downloaded); + }); + }, [component.name]); + + return { downloaded, downloading }; +}; + +// A download-required model must be downloaded before it can be trained — +// otherwise clicking Train silently re-triggers a download for a model the +// user just deleted. Shared by every place a run's train/retrain action is +// gated on its model's download state (RunCard, ModelCardCompact, +// ModelDetailView). +export const useModelDownloadGate = (model, runModelName) => { + const { downloaded, downloading } = useComponentDownloadState( + model || { name: runModelName }, + ); + const modelNotDownloaded = + Boolean(model?.metadata?.requires_download) && + !(downloaded && !downloading); + + return { modelNotDownloaded }; +}; + +// Delete a component's download and broadcast the new state to every control. +export const deleteComponent = async ({ + component, + enqueueSnackbar, + t, + onStatusChange, +}) => { + try { + await deleteComponentDownload(component.name); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: false, + }); + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.deleted"), { + variant: "success", + }); + } catch { + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } +}; + +const ComponentDownloadControl = ({ component, onStatusChange }) => { + const { t } = useTranslation(["common"]); + const { enqueueSnackbar } = useSnackbar(); + const meta = component.metadata || {}; + const { downloaded, downloading } = useComponentDownloadState(component); + const [confirmOpen, setConfirmOpen] = useState(false); + + if (!meta.requires_download) return null; + + const handleDownload = () => + startComponentDownload({ component, enqueueSnackbar, t, onStatusChange }); + + const handleDelete = () => + deleteComponent({ component, enqueueSnackbar, t, onStatusChange }); + + if (downloading) { + return ( + + + {t("common:componentDownload.downloading")} + + + + ); + } + + if (downloaded) { + return ( + <> + + setConfirmOpen(false)} + onConfirm={() => { + setConfirmOpen(false); + handleDelete(); + }} + content={t("common:componentDownload.confirmDelete", { + name: component.display_name || component.name, + })} + /> + + ); + } + + return ( + + ); +}; + +export default ComponentDownloadControl; diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx new file mode 100644 index 000000000..628c9958d --- /dev/null +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx @@ -0,0 +1,73 @@ +import React from "react"; +import { screen, fireEvent, waitFor } from "@testing-library/react"; +import { renderWithProviders } from "../../../test-utils/renderWithProviders"; + +jest.mock("../../../api/component", () => ({ + downloadComponent: jest.fn(() => Promise.resolve({ id: "job-1" })), + deleteComponentDownload: jest.fn(() => Promise.resolve()), + getComponentDownloadStatus: jest.fn(() => + Promise.resolve({ downloaded: false, requires_download: true }), + ), +})); +jest.mock("../../../utils/jobPoller", () => ({ + startJobPolling: jest.fn(), + stopJobPolling: jest.fn(), + subscribeJobs: jest.fn(() => () => {}), +})); + +import ComponentDownloadControl from "./ComponentDownloadControl"; +import { + downloadComponent, + deleteComponentDownload, +} from "../../../api/component"; + +const component = { + name: "OpusMtEnRoaTransformer", + downloaded: false, + metadata: { requires_download: true, download_size_bytes: 310000000 }, +}; + +describe("ComponentDownloadControl", () => { + it("shows a download button with the size and triggers download", async () => { + renderWithProviders( + {}} + />, + ); + const button = await screen.findByRole("button", { name: /download/i }); + fireEvent.click(button); + await waitFor(() => + expect(downloadComponent).toHaveBeenCalledWith("OpusMtEnRoaTransformer"), + ); + }); + + it("shows a delete control for a downloaded component and deletes it", async () => { + // A distinct name avoids the module-level download-state cache carrying + // over from the download test above. + const downloadedComponent = { + ...component, + name: "OpusMtEnRoaTransformerDownloaded", + downloaded: true, + }; + renderWithProviders( + {}} + />, + ); + const button = await screen.findByRole("button", { + name: /delete download/i, + }); + fireEvent.click(button); + // Deletion only happens after confirming in the modal. + expect(deleteComponentDownload).not.toHaveBeenCalled(); + const confirm = await screen.findByRole("button", { name: "Delete" }); + fireEvent.click(confirm); + await waitFor(() => + expect(deleteComponentDownload).toHaveBeenCalledWith( + "OpusMtEnRoaTransformerDownloaded", + ), + ); + }); +}); diff --git a/DashAI/front/src/components/models/model/HoverModelInfo.jsx b/DashAI/front/src/components/models/model/HoverModelInfo.jsx index 9173ab61f..60a5ba0fb 100644 --- a/DashAI/front/src/components/models/model/HoverModelInfo.jsx +++ b/DashAI/front/src/components/models/model/HoverModelInfo.jsx @@ -3,13 +3,24 @@ import { Box, Typography, Popover } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material/styles"; +const formatSize = (bytes) => { + if (bytes == null) return null; + const mb = bytes / 1024 / 1024; + if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`; + return `${Math.round(mb)} MB`; +}; + export default function HoverModelInfo({ anchorEl, hoveredModel, handleMouseLeave, }) { - const { t } = useTranslation(["common"]); + const { t } = useTranslation(["common", "custom"]); const theme = useTheme(); + const size = formatSize( + hoveredModel?.metadata?.download_size_bytes || + hoveredModel?.download_size_bytes, + ); return ( + + {size && ( + + {t("custom:modelSize", { size })} + + )} )} diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx new file mode 100644 index 000000000..975c57360 --- /dev/null +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx @@ -0,0 +1,89 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, CircularProgress, IconButton, Tooltip } from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import { + useComponentDownloadState, + deleteComponent, +} from "./ComponentDownloadControl"; +import DeleteConfirmationModal from "../../threeSectionLayout/DeleteConfirmationModal"; + +/** + * Compact, tooltip-free download status shown at the end of a model row in the + * models side bar. The row click starts the download, so this is only an + * indicator: a spinner while downloading, a delete icon for a downloaded model, + * and a plain download icon otherwise. + * @param {object} model - The model component dict. + * @param {function} onChanged - Called after a delete so the list can refresh. + */ +export default function ModelDownloadStatusIcon({ model, onChanged }) { + const { t } = useTranslation(["common"]); + const { enqueueSnackbar } = useSnackbar(); + const { downloaded, downloading } = useComponentDownloadState(model); + const [confirmOpen, setConfirmOpen] = useState(false); + + if (!model.metadata?.requires_download) return null; + + if (downloading) { + return ; + } + + if (downloaded) { + return ( + <> + + { + e.stopPropagation(); + setConfirmOpen(true); + }} + > + + + + setConfirmOpen(false)} + onConfirm={() => { + setConfirmOpen(false); + deleteComponent({ + component: model, + enqueueSnackbar, + t, + onStatusChange: onChanged, + }); + }} + content={t("common:componentDownload.confirmDelete", { + name: model.display_name || model.name, + })} + /> + + ); + } + + // The row click handles the download; the icon is a non-interactive hint. + return ( + + + + ); +} + +ModelDownloadStatusIcon.propTypes = { + model: PropTypes.object.isRequired, + onChanged: PropTypes.func, +}; diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx new file mode 100644 index 000000000..6c17a3b16 --- /dev/null +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx @@ -0,0 +1,54 @@ +import React from "react"; +import { screen, fireEvent, waitFor } from "@testing-library/react"; +import { renderWithProviders } from "../../../test-utils/renderWithProviders"; + +jest.mock("../../../api/component", () => ({ + downloadComponent: jest.fn(() => Promise.resolve({ id: "job-1" })), + deleteComponentDownload: jest.fn(() => Promise.resolve()), + getComponentDownloadStatus: jest.fn(() => + Promise.resolve({ downloaded: false, requires_download: true }), + ), +})); +jest.mock("../../../utils/jobPoller", () => ({ + startJobPolling: jest.fn(), + stopJobPolling: jest.fn(), + subscribeJobs: jest.fn(() => () => {}), +})); + +import ModelDownloadStatusIcon from "./ModelDownloadStatusIcon"; +import { deleteComponentDownload } from "../../../api/component"; + +const model = { + name: "DownloadableTestModel", + downloaded: false, + metadata: { requires_download: true, download_size_bytes: 268435456 }, +}; + +describe("ModelDownloadStatusIcon", () => { + it("renders no interactive control for an undownloaded model", () => { + renderWithProviders( + {}} />, + ); + expect(screen.queryByRole("button")).toBeNull(); + }); + + it("deletes a downloaded model after confirming in the modal", async () => { + renderWithProviders( + {}} + />, + ); + const del = await screen.findByRole("button", { name: "Delete download" }); + fireEvent.click(del); + // Deletion only happens after confirming in the modal. + expect(deleteComponentDownload).not.toHaveBeenCalled(); + const confirm = await screen.findByRole("button", { name: "Delete" }); + fireEvent.click(confirm); + await waitFor(() => + expect(deleteComponentDownload).toHaveBeenCalledWith( + "DownloadableTestModel", + ), + ); + }); +}); diff --git a/DashAI/front/src/components/models/model/ModelListItem.jsx b/DashAI/front/src/components/models/model/ModelListItem.jsx index c846e9baa..316d03051 100644 --- a/DashAI/front/src/components/models/model/ModelListItem.jsx +++ b/DashAI/front/src/components/models/model/ModelListItem.jsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { Box, Typography, Tooltip } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import HoverModelInfo from "./HoverModelInfo"; import { ModelIcon } from "./ModelIcon"; @@ -8,7 +8,12 @@ import { setCustomDragImage } from "../../../utils/dragImage"; export default function ModelListItem({ model, disabled = false, + draggable = true, + dragType = "application/x-dashai-model", + dragPayload, onClick, + onDisabledClick, + action = null, ...props }) { const theme = useTheme(); @@ -16,10 +21,8 @@ export default function ModelListItem({ const [hoveredModel, setHoveredModel] = useState(null); const handleMouseEnter = (event, model) => { - if (!disabled) { - setAnchorEl(event.currentTarget); - setHoveredModel(model); - } + setAnchorEl(event.currentTarget); + setHoveredModel(model); }; const handleMouseLeave = () => { @@ -27,6 +30,16 @@ export default function ModelListItem({ setHoveredModel(null); }; + const handleCardClick = (event) => { + if (disabled) { + if (onDisabledClick) onDisabledClick(event); + return; + } + if (onClick) onClick(event); + }; + + const isClickable = Boolean(onClick || onDisabledClick); + // Get color and icon from metadata or use defaults const color = model.color || model.metadata?.color || theme.palette.text.secondary; @@ -34,133 +47,123 @@ export default function ModelListItem({ return ( <> - { + e.dataTransfer.setData( + dragType, + JSON.stringify(dragPayload ?? model), + ); + e.dataTransfer.effectAllowed = "copy"; + setCustomDragImage(e); + } + : undefined + } + onMouseEnter={(e) => handleMouseEnter(e, model)} + onMouseLeave={handleMouseLeave} + // Close the hover popover on any click in the row (capture phase, so it + // runs even for the action's icon which stops propagation). Otherwise a + // click that opens a modal (e.g. delete confirmation) leaves the popover + // stuck open since no mouseleave fires. + onClickCapture={handleMouseLeave} + onClick={handleCardClick} + {...props} + sx={{ + display: "flex", + alignItems: "center", + gap: 3, + p: 3, + bgcolor: disabled ? theme.palette.ui.disabled : theme.palette.ui.box, + border: `1px solid ${theme.palette.ui.border}`, + borderRadius: 1, + cursor: isClickable ? "pointer" : "default", + transition: "all 0.2s", + position: "relative", + "&:hover": { + bgcolor: disabled + ? theme.palette.ui.disabled + : theme.palette.action.hover, + // A not downloaded row is disabled but still clickable to start the + // download, so give it the same hover feedback (border highlight + + // slide) instead of feeling stiff. + borderColor: color, + transform: !isClickable ? "none" : "translateX(4px)", }, + "&::after": disabled + ? { + content: '""', + position: "absolute", + inset: 0, + borderRadius: 1, + pointerEvents: "none", + background: + "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.1) 10px, rgba(0, 0, 0, 0.1) 20px)", + } + : {}, }} > + {/* Icon */} { - e.dataTransfer.setData( - "application/x-dashai-model", - JSON.stringify(model), - ); - e.dataTransfer.effectAllowed = "copy"; - setCustomDragImage(e); - } - : undefined - } - onMouseEnter={(e) => handleMouseEnter(e, model)} - onMouseLeave={handleMouseLeave} - onClick={disabled ? null : onClick} - {...props} sx={{ display: "flex", alignItems: "center", - gap: 3, - p: 3, + justifyContent: "center", + width: 36, + height: 36, + borderRadius: 1, bgcolor: disabled ? theme.palette.ui.disabled - : theme.palette.ui.box, - border: `1px solid ${theme.palette.ui.border}`, - borderRadius: 1, - cursor: disabled ? "not-allowed" : "grab", - transition: "all 0.2s", - opacity: disabled ? 0.5 : 1, - filter: disabled ? "grayscale(0.6)" : "none", - position: "relative", - "&:hover": { - bgcolor: disabled - ? theme.palette.ui.disabled - : theme.palette.action.hover, - borderColor: disabled ? theme.palette.ui.border : color, - transform: disabled ? "none" : "translateX(4px)", - }, - "&::after": disabled - ? { - content: '""', - position: "absolute", - inset: 0, - borderRadius: 1, - pointerEvents: "none", - background: - "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.1) 10px, rgba(0, 0, 0, 0.1) 20px)", - } - : {}, + : theme.palette.ui.border, + color: disabled + ? theme.palette.text.disabled + : theme.palette.text.primary, + flexShrink: 0, }} > - {/* Icon */} - + + + {/* Content */} + + - - + {model.display_name || model.name} + + - {/* Content */} - - - {model.display_name || model.name} - + {/* Trailing action (e.g. download/delete control) */} + {action && ( + + {action} - - - {!disabled && ( + )} + + { - )} + } ); } diff --git a/DashAI/front/src/components/models/modelSession/ModelsTable.jsx b/DashAI/front/src/components/models/modelSession/ModelsTable.jsx index ca559a8a1..fcaec8a58 100644 --- a/DashAI/front/src/components/models/modelSession/ModelsTable.jsx +++ b/DashAI/front/src/components/models/modelSession/ModelsTable.jsx @@ -158,6 +158,7 @@ function ModelsTable({ newExp, setNewExp }) { handleSelectedMetric(metricName, row.original.id) } required + variant="standard" /> ); }, diff --git a/DashAI/front/src/components/models/modelSession/ModelsTableSelectMetric.jsx b/DashAI/front/src/components/models/modelSession/ModelsTableSelectMetric.jsx index 4b9e1c786..4c5efdc5c 100644 --- a/DashAI/front/src/components/models/modelSession/ModelsTableSelectMetric.jsx +++ b/DashAI/front/src/components/models/modelSession/ModelsTableSelectMetric.jsx @@ -1,5 +1,5 @@ import { MenuItem, TextField } from "@mui/material"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import useMetricsByTask from "../../../hooks/useMetricByTask"; function ModelsTableSelectMetric({ @@ -7,10 +7,36 @@ function ModelsTableSelectMetric({ metricName, handleSelectedMetric, required = false, + variant = "outlined", + autoSelectDefault = false, }) { - const { compatibleMetrics } = useMetricsByTask({ taskName }); + const { compatibleMetrics, loading } = useMetricsByTask({ taskName }); const [selectedMetric, setSelectedMetric] = useState(metricName); + // Opt-in only: the run-edit/add-model wizard wants a default goal metric + // like its other fields (optimizer, its parameters) already have, but the + // batch-experiment table (ModelsTable.jsx) relies on this field staying + // empty/required so each row forces an explicit per-row choice — leave + // that behavior untouched for callers that don't ask for a default. + useEffect(() => { + if ( + autoSelectDefault && + !selectedMetric && + !loading && + compatibleMetrics.length > 0 + ) { + const defaultMetric = compatibleMetrics[0].name; + setSelectedMetric(defaultMetric); + handleSelectedMetric(defaultMetric); + } + }, [ + autoSelectDefault, + selectedMetric, + loading, + compatibleMetrics, + handleSelectedMetric, + ]); + const handleChange = (e) => { const goalMetric = e.target.value; setSelectedMetric(goalMetric); @@ -23,10 +49,21 @@ function ModelsTableSelectMetric({ value={selectedMetric || ""} onChange={handleChange} size="small" - variant="standard" + variant={variant} fullWidth required={required} - error={required && !selectedMetric} + // Without autoSelectDefault, an empty selection is always a real error + // once loaded (ModelsTable.jsx's batch rows require an explicit + // per-row choice). With it, a non-empty list with nothing selected yet + // is only the one render between the list loading and the auto-select + // effect above filling it in — not a real error — so only a truly + // empty list (no compatible metric to default to) is worth flagging. + error={ + required && + !selectedMetric && + !loading && + (!autoSelectDefault || compatibleMetrics.length === 0) + } slotProps={{ MenuProps: { PaperProps: { diff --git a/DashAI/front/src/components/models/modelSession/OptimizationTable.jsx b/DashAI/front/src/components/models/modelSession/OptimizationTable.jsx index 9d203081c..122db70f1 100644 --- a/DashAI/front/src/components/models/modelSession/OptimizationTable.jsx +++ b/DashAI/front/src/components/models/modelSession/OptimizationTable.jsx @@ -119,6 +119,7 @@ function OptimizationTable({ newExp, setNewExp }) { row.original.id, ) } + variant="standard" /> ), }, diff --git a/DashAI/front/src/components/models/modelSession/OptimizationTableSelectOptimizer.jsx b/DashAI/front/src/components/models/modelSession/OptimizationTableSelectOptimizer.jsx index 78c1599bd..d3bcb63f8 100644 --- a/DashAI/front/src/components/models/modelSession/OptimizationTableSelectOptimizer.jsx +++ b/DashAI/front/src/components/models/modelSession/OptimizationTableSelectOptimizer.jsx @@ -7,6 +7,7 @@ function OptimizationTableSelectOptimizer({ taskName, optimizerName, handleSelectedOptimizer, + variant = "outlined", }) { const { compatibleModels } = useOptimizersByTask({ taskName }); const [selectedOptimizer, setSelectedOptimizer] = useState(optimizerName); @@ -31,7 +32,7 @@ function OptimizationTableSelectOptimizer({ }} fullWidth size="small" - variant="standard" + variant={variant} slotProps={{ MenuProps: { PaperProps: { diff --git a/DashAI/front/src/components/models/modelSession/PrepareDatasetStep.jsx b/DashAI/front/src/components/models/modelSession/PrepareDatasetStep.jsx index 7cfa7c969..93bc05c53 100644 --- a/DashAI/front/src/components/models/modelSession/PrepareDatasetStep.jsx +++ b/DashAI/front/src/components/models/modelSession/PrepareDatasetStep.jsx @@ -37,15 +37,11 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { const [infoLoading, setInfoLoading] = useState(true); const { t } = useTranslation(["experiments", "common"]); - const [taskRequirements, setTaskRequirements] = useState({ - name: "", - metadata: { - inputs_types: [], - inputs_cardinality: "", - outputs_types: [], - outputs_cardinality: "", - }, - }); + // null means "not fetched yet" — distinct from the empty-but-loaded shape + // getTaskRequirements falls back to when the task genuinely isn't found. + // The banner below only renders once this is non-null, otherwise it briefly + // interpolates its message with blank task name/types/cardinality. + const [taskRequirements, setTaskRequirements] = useState(null); const [inputColumnNames, setInputColumnNames] = useState( newExp.input_columns, @@ -56,6 +52,11 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { const [columnsReady, setColumnsReady] = useState(false); const [columnsAreValid, setColumnsAreValid] = useState(false); + // True until the current column selection has actually been checked against + // the backend at least once — distinct from columnsAreValid=false, so the + // banner doesn't flash red while columns are still being auto-selected or a + // check is in flight, only once a real valid/invalid result is known. + const [validationPending, setValidationPending] = useState(true); const [shuffle, setShuffle] = useState(true); const [stratify, setStratify] = useState(false); const [seed, setSeed] = useState(42); @@ -189,21 +190,21 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { }; const validateColumns = async () => { - if ( - !datasetInfo || - !datasetInfo.column_names || - datasetInfo.column_names.length === 0 - ) { - setColumnsAreValid(false); - return; - } + try { + if ( + !datasetInfo || + !datasetInfo.column_names || + datasetInfo.column_names.length === 0 + ) { + setColumnsAreValid(false); + return; + } - if (inputColumnNames.length === 0 || outputColumnNames.length === 0) { - setColumnsAreValid(false); - return; - } + if (inputColumnNames.length === 0 || outputColumnNames.length === 0) { + setColumnsAreValid(false); + return; + } - try { const validation = await validateColumnsRequest( newExp.task_name, dataset.id, @@ -221,6 +222,8 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { console.error("Unknown Error", error.message); } setColumnsAreValid(false); + } finally { + setValidationPending(false); } }; @@ -277,9 +280,11 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { datasetInfo.column_names && datasetInfo.column_names.length > 0 ) { + setValidationPending(true); validateColumns(); } else { setColumnsAreValid(false); + setValidationPending(true); } }, [ columnsReady, @@ -430,81 +435,79 @@ function PrepareDatasetStep({ newExp, setNewExp, setNextEnabled, dataset }) { ) : null ) : null} - - `${theme.palette[columnsAreValid ? "success" : "error"].main}40`, - border: (theme) => - `1px solid ${theme.palette[columnsAreValid ? "success" : "error"].main}`, - }} - data-tour="models-validation-alert" - > - - {taskRequirements - ? t( - columnsAreValid - ? "experiments:label.columnsValidRequirements" - : "experiments:label.columnsInvalidRequirements", - { taskName: taskRequirements.display_name }, - ) - : null} - - - - - - The input columns must be of the types - {taskRequirements - ? renderTypesAsChips(taskRequirements.metadata.inputs_types) - : null} - - , and they should have a cardinality of + {taskRequirements && !validationPending && ( + + `${theme.palette[columnsAreValid ? "success" : "error"].main}40`, + border: (theme) => + `1px solid ${theme.palette[columnsAreValid ? "success" : "error"].main}`, + }} + data-tour="models-validation-alert" + > + + {t( + columnsAreValid + ? "experiments:label.columnsValidRequirements" + : "experiments:label.columnsInvalidRequirements", + { taskName: taskRequirements.display_name }, + )} + + + + + + The input columns must be of the types + {renderTypesAsChips(taskRequirements.metadata.inputs_types)} + , and they should have a cardinality of + + {{ + cardinality: + taskRequirements.metadata.inputs_cardinality, + }} + . + + + + + + + + + The output columns must be of the types + {renderTypesAsChips(taskRequirements.metadata.outputs_types)} + + , and they should have a cardinality of {{ - cardinality: taskRequirements.metadata.inputs_cardinality, + cardinality: + taskRequirements.metadata.outputs_cardinality, }} . - - - - - - - - The output columns must be of the types - {taskRequirements - ? renderTypesAsChips(taskRequirements.metadata.outputs_types) - : null} - - , and they should have a cardinality of - {{ - cardinality: taskRequirements.metadata.outputs_cardinality, - }} - . - - - + + + - - + + )} {!infoLoading ? ( diff --git a/DashAI/front/src/components/models/runResults/ExplainerResultsTab.jsx b/DashAI/front/src/components/models/runResults/ExplainerResultsTab.jsx new file mode 100644 index 000000000..48d5eede8 --- /dev/null +++ b/DashAI/front/src/components/models/runResults/ExplainerResultsTab.jsx @@ -0,0 +1,201 @@ +import React, { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, ToggleButton } from "@mui/material"; +import { alpha } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { Virtuoso } from "react-virtuoso"; + +import PillToggleButtonGroup from "../../shared/PillToggleButtonGroup"; +import ExplainersCard from "../../explainers/ExplainersCard"; +import LazyExplainerCard from "../../explainers/LazyExplainerCard"; +import { + explainerCacheKey, + DEFAULT_CARD_HEIGHT, +} from "../../explainers/explainerCache"; + +/** + * The Explainability tab: a scope toggle (global / local) over the run's + * explainer cards. In the full-height detail view the list is virtualized + * against the outer scroller and a just-created explainer is scrolled into + * view and flashed; the inline (collapsible card) view renders a plain column. + */ +export default function ExplainerResultsTab({ + activeExplainers, + explainerFilter, + setExplainerFilter, + fillHeight, + scrollParent, + explainerDisplayNames, + cardHeightsRef, + getCacheEntry, + updateCacheEntry, + newExplainerKey, + setNewExplainerKey, + highlightedExplainerKey, + setHighlightedExplainerKey, + onDelete, +}) { + const { t } = useTranslation(["models"]); + const virtuosoRef = useRef(null); + + // Read in the deferred callback so it sees the list after the scope switch. + const activeExplainersRef = useRef(activeExplainers); + useEffect(() => { + activeExplainersRef.current = activeExplainers; + }, [activeExplainers]); + + // Scroll a new explainer card into view and flash it, like the notebooks + // view. The delay lets the scope switch settle before scrolling. + useEffect(() => { + if (!newExplainerKey) return undefined; + const scrollTimer = setTimeout(() => { + const list = activeExplainersRef.current; + const scope = newExplainerKey.startsWith("global-") ? "global" : "local"; + const id = Number(newExplainerKey.slice(scope.length + 1)); + const index = list.findIndex((e) => e.id === id); + const targetIndex = index >= 0 ? index : list.length - 1; + if (virtuosoRef.current && targetIndex >= 0) { + // align end, not center: a new explainer is appended last, so pinning + // its bottom lands the scroller fully down; followOutput holds it as + // the plot loads and the card grows. + virtuosoRef.current.scrollToIndex({ + index: targetIndex, + align: "end", + behavior: "smooth", + }); + } + setHighlightedExplainerKey(newExplainerKey); + setNewExplainerKey(null); + }, 100); + return () => clearTimeout(scrollTimer); + }, [newExplainerKey, setHighlightedExplainerKey, setNewExplainerKey]); + + return ( + + {/* Scope selector pinned to the upper right of the content, so it stays + visible while the card list scrolls. */} + + { + if (newValue !== null) setExplainerFilter(newValue); + }} + sx={{ + bgcolor: (theme) => alpha(theme.palette.ui.box, 0.8), + backdropFilter: "blur(8px)", + }} + > + + {t("models:label.globalExplainers")} + + + {t("models:label.localExplainers")} + + + + + {activeExplainers.length === 0 ? ( + + {explainerFilter === "global" + ? t("models:label.noGlobalExplainersYet") + : t("models:label.noLocalExplainersYet")} + + ) : fillHeight ? ( + // Virtualize against the scroller so many plugin cards stay cheap. + // Keyed on scope so toggling scrolls back to the bottom. + ( + // pb, not flex gap: virtualized items are not flex children. px + // gives the highlight ring room so the scroller does not clip its + // sides. + + + + )} + /> + ) : ( + // Inline view: no bounded scroller, so keep the flex column. px gives + // the highlight ring room at the sides. + + {activeExplainers.map((explainer) => { + const key = explainerCacheKey(explainerFilter, explainer); + return ( + updateCacheEntry(key, patch)} + isHighlighted={ + highlightedExplainerKey === + `${explainerFilter}-${explainer.id}` + } + compact + /> + ); + })} + + )} + + ); +} + +ExplainerResultsTab.propTypes = { + activeExplainers: PropTypes.array.isRequired, + explainerFilter: PropTypes.oneOf(["global", "local"]).isRequired, + setExplainerFilter: PropTypes.func.isRequired, + fillHeight: PropTypes.bool, + scrollParent: PropTypes.instanceOf(Element), + explainerDisplayNames: PropTypes.object.isRequired, + cardHeightsRef: PropTypes.shape({ current: PropTypes.object }).isRequired, + getCacheEntry: PropTypes.func.isRequired, + updateCacheEntry: PropTypes.func.isRequired, + newExplainerKey: PropTypes.string, + setNewExplainerKey: PropTypes.func.isRequired, + highlightedExplainerKey: PropTypes.string, + setHighlightedExplainerKey: PropTypes.func.isRequired, + onDelete: PropTypes.func, +}; diff --git a/DashAI/front/src/components/models/runResults/PredictionResultsTab.jsx b/DashAI/front/src/components/models/runResults/PredictionResultsTab.jsx new file mode 100644 index 000000000..a86750667 --- /dev/null +++ b/DashAI/front/src/components/models/runResults/PredictionResultsTab.jsx @@ -0,0 +1,298 @@ +import React, { useState, useRef, useMemo } from "react"; +import PropTypes from "prop-types"; +import { + Box, + Typography, + Button, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + ToggleButton, +} from "@mui/material"; +import { LoadingButton } from "@mui/lab"; +import { + Close as CloseIcon, + AddCircleOutline, + PlayArrow as PlayArrowIcon, +} from "@mui/icons-material"; +import { alpha } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; + +import PillToggleButtonGroup from "../../shared/PillToggleButtonGroup"; +import PredictionCard from "../PredictionCard"; +import ManualPredictionsTable from "../ManualPredictionsTable"; +import DatasetPredictionPanel from "../DatasetPredictionPanel"; + +/** + * The Predictions tab: a dataset / manual toggle over the run's predictions. + * Dataset predictions render as cards created through a dialog; manual ones + * render in an editable table. Panel visibility is controlled by the parent so + * an external "open prediction dialog" event can trigger it. + */ +export default function PredictionResultsTab({ + run, + session, + predictions, + predictionDisplayNumbers, + outputColumn, + trainingDatasetSample, + showDatasetPanel, + setShowDatasetPanel, + onSaved, + onDelete, + onUpdate, +}) { + const { t } = useTranslation(["models", "common", "prediction"]); + const [predictionFilter, setPredictionFilter] = useState("dataset"); + const datasetRunRef = useRef(null); + const [datasetRunState, setDatasetRunState] = useState({ + canRun: false, + isSubmitting: false, + }); + // Add Row / Run Prediction for the manual table live here (next to the + // Dataset/Manual toggle) so both prediction types share one header layout. + // ManualPredictionsTable exposes its actions through this ref and reports + // button-enabled state through onStateChange. + const manualActionsRef = useRef(null); + const [manualRunState, setManualRunState] = useState({ + canAddRow: false, + canRun: false, + isRunning: false, + }); + + const visiblePredictions = useMemo( + () => + predictions.filter((p) => + predictionFilter === "dataset" ? p.dataset_id : !p.dataset_id, + ), + [predictions, predictionFilter], + ); + + return ( + + {predictionFilter === "dataset" ? ( + + + + ) : ( + + + } + onClick={() => manualActionsRef.current?.runPrediction()} + disabled={!manualRunState.canRun} + loading={manualRunState.isRunning} + sx={{ textTransform: "none", fontWeight: 500 }} + > + {t("prediction:button.runPrediction")} + + + )} + + {/* Spans every row below it so its containing block covers the whole + scrollable list, not just this header row - otherwise it would stop + sticking as soon as the header row itself scrolls out of view. + zIndex above the Manual table's own sticky header (max z-index 2, see + leanDatasetTable.css) so it always paints on top instead of being + covered once both reach top:0. */} + + { + if (newValue !== null) setPredictionFilter(newValue); + }} + sx={{ + bgcolor: (theme) => alpha(theme.palette.ui.box, 0.8), + backdropFilter: "blur(8px)", + }} + > + + {t("models:label.datasetPredictions")} + + + {t("models:label.manualPredictions")} + + + + + + setShowDatasetPanel(false)} + maxWidth="md" + fullWidth + PaperProps={{ sx: { minHeight: "500px" } }} + > + + + + {t("models:button.newDatasetPrediction")} + + setShowDatasetPanel(false)} + sx={{ color: "text.secondary" }} + > + + + + + + { + onSaved(prediction); + setShowDatasetPanel(false); + }} + onClose={() => setShowDatasetPanel(false)} + runRef={datasetRunRef} + onStateChange={setDatasetRunState} + /> + + + + datasetRunRef.current?.()} + > + {t("prediction:button.runPrediction")} + + + + + {predictionFilter === "manual" ? ( + + ) : visiblePredictions.length === 0 ? ( + + {t("models:label.noDatasetPredictionsYet")} + + ) : ( + + {visiblePredictions.map((prediction) => ( + + ))} + + )} + + + ); +} + +PredictionResultsTab.propTypes = { + run: PropTypes.object.isRequired, + session: PropTypes.object, + predictions: PropTypes.array.isRequired, + predictionDisplayNumbers: PropTypes.instanceOf(Map).isRequired, + outputColumn: PropTypes.string, + trainingDatasetSample: PropTypes.object, + showDatasetPanel: PropTypes.bool.isRequired, + setShowDatasetPanel: PropTypes.func.isRequired, + onSaved: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/models/runResults/ResultsTabsHeader.jsx b/DashAI/front/src/components/models/runResults/ResultsTabsHeader.jsx new file mode 100644 index 000000000..61ddb7af4 --- /dev/null +++ b/DashAI/front/src/components/models/runResults/ResultsTabsHeader.jsx @@ -0,0 +1,153 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, Typography, Tab, Tooltip, Chip } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PillTabs from "../../shared/PillTabs"; + +const groupLabelSx = { + textTransform: "uppercase", + letterSpacing: 0.5, + fontWeight: 600, + pl: 2, + pt: 1, +}; + +// Matches the MUI small Chip's height, so tabs with a count chip don't grow +// taller than plain-text tabs and push their label off-center. +const TAB_LABEL_HEIGHT = 24; +const tabLabelRowSx = { + display: "flex", + alignItems: "center", + gap: 2, + height: TAB_LABEL_HEIGHT, +}; + +/** + * The two grouped pill tab bars (Metrics: Live/Hyperparameters, Operations: + * Explainability/Predictions) shown above a run's results, with a vertical + * rule between the groups. Purely presentational. + */ +export default function ResultsTabsHeader({ + activeTab, + onTabChange, + isFinished, + optimizables, + explainerCount, + predictionCount, +}) { + const { t } = useTranslation(["models"]); + + // Explains *why* a tab is disabled, so it reads as a real (if currently + // unavailable) tab rather than being confused with the static group labels. + const notFinishedTooltip = !isFinished + ? t("models:message.tabAvailableAfterFinish") + : ""; + const hyperparametersTooltip = !isFinished + ? notFinishedTooltip + : optimizables === 0 + ? t("models:message.noOptimizableParamsForHpo") + : ""; + + return ( + + + + {t("models:label.metrics")} + + onTabChange(newValue)} + aria-label="Result characteristics tabs" + > + {t("models:label.liveMetrics")} + } + /> + + + {t("models:label.hyperparameters")} + + + } + disabled={!isFinished || optimizables === 0} + /> + + + + {/* Empty spacer just for the horizontal gap between groups. Kept out of + the flex height/alignment calculation so the actual rule (positioned + absolutely inside it) can be sized freely without pushing the tabs + around. */} + + + + + + + {t("models:label.operations")} + + onTabChange(newValue)} + aria-label="Result operations tabs" + > + + + {t("models:label.explainability")} + {isFinished && ( + + )} + + + } + disabled={!isFinished} + /> + + + {t("models:label.predictions")} + {isFinished && ( + + )} + + + } + disabled={!isFinished} + /> + + + + ); +} + +ResultsTabsHeader.propTypes = { + activeTab: PropTypes.number.isRequired, + onTabChange: PropTypes.func.isRequired, + isFinished: PropTypes.bool, + optimizables: PropTypes.number, + explainerCount: PropTypes.number, + predictionCount: PropTypes.number, +}; diff --git a/DashAI/front/src/components/models/runResults/useRunResultsData.js b/DashAI/front/src/components/models/runResults/useRunResultsData.js new file mode 100644 index 000000000..4f69589bd --- /dev/null +++ b/DashAI/front/src/components/models/runResults/useRunResultsData.js @@ -0,0 +1,278 @@ +import { useState, useEffect, useCallback, useMemo, useRef } from "react"; + +import { getExplainers } from "../../../api/explainer"; +import { getComponents } from "../../../api/component"; +import { getPredictions } from "../../../api/predict"; +import { getModelSessionById } from "../../../api/modelSession"; +import { getDatasetSample } from "../../../api/datasets"; +import { + EMPTY_EXPLAINER_ENTRY, + explainerCacheKey, + mergeExplainers, +} from "../../explainers/explainerCache"; + +/** + * Owns everything data related for a run's results: the explainer and + * prediction lists, their fetching/polling, the per card plot cache, the + * shared explainer display names, the training dataset sample used by the + * prediction cards, and the "a new explainer just appeared" signal that drives + * the scroll-and-flash in the explainer tab. Keeping it here leaves RunResults + * and the tab components purely about layout. + */ +export default function useRunResultsData({ + run, + session, + onRefresh, + explainerRefreshTrigger, +}) { + const runId = run.id; + + const [globalExplainers, setGlobalExplainers] = useState([]); + const [localExplainers, setLocalExplainers] = useState([]); + const [predictions, setPredictions] = useState([]); + + const [trainingDatasetSample, setTrainingDatasetSample] = useState(null); + const [outputColumn, setOutputColumn] = useState(null); + + // "global" | "local", which explainer scope is shown. + const [explainerFilter, setExplainerFilter] = useState("global"); + // A just added explainer: newExplainerKey triggers the scroll and flash; + // highlightedExplainerKey drives the ring animation. + const [newExplainerKey, setNewExplainerKey] = useState(null); + const [highlightedExplainerKey, setHighlightedExplainerKey] = useState(null); + // Last measured height per card key, so placeholders reserve exact space. + const cardHeightsRef = useRef({}); + // Explainer keys seen on the previous fetch; null until the first load so + // the initial batch is not treated as newly added. + const seenExplainerKeysRef = useRef(null); + // Explainer component name to display name, fetched once and shared so cards + // do not each fetch it (a per card fetch shifted heights). + const [explainerDisplayNames, setExplainerDisplayNames] = useState({}); + // Per card plot state (items, edits, selection), so the list can unmount + // offscreen cards without refetching or losing edits. + const [explainerCache, setExplainerCache] = useState({}); + + const getCacheEntry = useCallback( + (key) => explainerCache[key] ?? EMPTY_EXPLAINER_ENTRY, + [explainerCache], + ); + const updateCacheEntry = useCallback((key, patch) => { + setExplainerCache((prev) => ({ + ...prev, + [key]: { ...(prev[key] ?? EMPTY_EXPLAINER_ENTRY), ...patch }, + })); + }, []); + + // "Prediction #N" counts only this run's own predictions, numbered per + // section (Dataset vs Manual) in creation order, not `prediction.id`, which + // is a database wide key shared across every run and session. + const predictionDisplayNumbers = useMemo(() => { + const numbers = new Map(); + [ + predictions.filter((p) => p.dataset_id), + predictions.filter((p) => !p.dataset_id), + ].forEach((group) => { + [...group] + .sort((a, b) => a.id - b.id) + .forEach((p, index) => numbers.set(p.id, index + 1)); + }); + return numbers; + }, [predictions]); + + // includePredictions lets the running explainer poll refetch only the + // explainers: predictions do not change during that window (their own + // completion is tracked by job polling), so refetching them every 3s just + // re renders the prediction cards for nothing. + const fetchOperations = useCallback( + async ({ includePredictions = true } = {}) => { + if (!runId) return; + + try { + const [globalExpls, localExpls, preds] = await Promise.all([ + getExplainers(runId, "global").catch(() => []), + getExplainers(runId, "local").catch(() => []), + includePredictions + ? getPredictions(runId).catch(() => []) + : Promise.resolve(null), + ]); + + // Detect explainers added since the last fetch. First fetch seeds the + // baseline; later fetches switch the scope toggle to the new explainer. + const currentKeys = new Set([ + ...globalExpls.map((e) => `global-${e.id}`), + ...localExpls.map((e) => `local-${e.id}`), + ]); + if (seenExplainerKeysRef.current === null) { + seenExplainerKeysRef.current = currentKeys; + } else { + const added = [...currentKeys].filter( + (key) => !seenExplainerKeysRef.current.has(key), + ); + seenExplainerKeysRef.current = currentKeys; + if (added.length > 0) { + const key = added[added.length - 1]; + setExplainerFilter(key.startsWith("global-") ? "global" : "local"); + setNewExplainerKey(key); + } + } + + setGlobalExplainers((prev) => mergeExplainers(prev, globalExpls)); + setLocalExplainers((prev) => mergeExplainers(prev, localExpls)); + if (preds !== null) setPredictions(preds); + + // Drop cached state for explainers that no longer exist (deleted on + // retrain), so the cache cannot grow or serve a stale entry. + const validKeys = new Set([ + ...globalExpls.map((e) => explainerCacheKey("global", e)), + ...localExpls.map((e) => explainerCacheKey("local", e)), + ]); + setExplainerCache((prev) => { + const next = {}; + let removed = false; + Object.keys(prev).forEach((key) => { + if (validKeys.has(key)) next[key] = prev[key]; + else removed = true; + }); + return removed ? next : prev; + }); + } catch (error) { + console.error("Error fetching operations:", error); + } + }, + [runId], + ); + + useEffect(() => { + fetchOperations(); + }, [fetchOperations, explainerRefreshTrigger]); + + // Fetch explainer display names once and share them with every card. + useEffect(() => { + getComponents({ selectTypes: ["GlobalExplainer", "LocalExplainer"] }) + .then((components) => { + const names = {}; + components.forEach((component) => { + names[component.name] = component.display_name || component.name; + }); + setExplainerDisplayNames(names); + }) + .catch(() => {}); + }, []); + + useEffect(() => { + const sessionId = run.model_session_id || session?.id; + if (!sessionId) return; + let cancelled = false; + getModelSessionById(sessionId) + .then((sessionData) => { + if (cancelled) return null; + setOutputColumn(sessionData.output_columns?.[0] ?? null); + return getDatasetSample(sessionData.dataset_id); + }) + .then((sample) => { + if (!cancelled && sample) setTrainingDatasetSample(sample); + }) + .catch(() => {}); + return () => { + cancelled = true; + }; + }, [run.model_session_id, session?.id]); + + // Refetch when run parameters change (after editing). Skip the first run: + // the mount fetch is already covered by the effect above, so firing here on + // mount too would double every initial request. + const paramsChangedFirstRun = useRef(true); + useEffect(() => { + if (paramsChangedFirstRun.current) { + paramsChangedFirstRun.current = false; + return; + } + fetchOperations(); + }, [ + run.parameters, + run.optimizer_parameters, + run.goal_metric, + fetchOperations, + ]); + + const hasRunningExplainers = + globalExplainers.some((e) => e.status === 1 || e.status === 2) || + localExplainers.some((e) => e.status === 1 || e.status === 2); + + useEffect(() => { + if (!hasRunningExplainers) return; + const interval = setInterval( + () => fetchOperations({ includePredictions: false }), + 3000, + ); + return () => clearInterval(interval); + }, [hasRunningExplainers, fetchOperations]); + + // Clear the highlight after the animation, in its own effect so setting + // newExplainerKey(null) elsewhere cannot cancel this timer (else the card + // stays flagged and the ring replays on every remount, e.g. tab switch). + useEffect(() => { + if (!highlightedExplainerKey) return undefined; + const timer = setTimeout(() => setHighlightedExplainerKey(null), 4000); + return () => clearTimeout(timer); + }, [highlightedExplainerKey]); + + // Explainers shown under the current scope toggle (global / local). + const activeExplainers = + explainerFilter === "global" ? globalExplainers : localExplainers; + + const handlePredictionCreated = useCallback( + (prediction) => { + if (prediction) { + setPredictions((prev) => { + const index = prev.findIndex((p) => p.id === prediction.id); + if (index === -1) return [prediction, ...prev]; + const updated = [...prev]; + updated[index] = prediction; + return updated; + }); + } else { + fetchOperations(); + } + if (onRefresh) onRefresh(); + }, + [fetchOperations, onRefresh], + ); + + // Stable so memoized cards keep their identity across RunResults renders + // (e.g. the 3s poll) instead of rerendering and restarting Plotly. + const handleExplainerDeleted = useCallback(() => { + fetchOperations(); + if (onRefresh) onRefresh(); + }, [fetchOperations, onRefresh]); + + const handlePredictionDeleted = useCallback(() => { + fetchOperations(); + if (onRefresh) onRefresh(); + }, [fetchOperations, onRefresh]); + + return { + globalExplainers, + localExplainers, + predictions, + activeExplainers, + explainerFilter, + setExplainerFilter, + newExplainerKey, + setNewExplainerKey, + highlightedExplainerKey, + setHighlightedExplainerKey, + explainerDisplayNames, + cardHeightsRef, + getCacheEntry, + updateCacheEntry, + hasRunningExplainers, + predictionDisplayNumbers, + outputColumn, + trainingDatasetSample, + fetchOperations, + handlePredictionCreated, + handleExplainerDeleted, + handlePredictionDeleted, + }; +} diff --git a/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx b/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx index 021a2261c..02c1768de 100644 --- a/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx +++ b/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx @@ -155,7 +155,6 @@ const ConverterTargetColumnModal = ({ variant="outlined" size="small" sx={{ - mr: 2, color: classColumnInitialValue === null ? "error.main" : "inherit", borderColor: classColumnInitialValue === null ? "error.main" : "inherit", diff --git a/DashAI/front/src/components/notebooks/converterCreation/ScopeStepConverter.jsx b/DashAI/front/src/components/notebooks/converterCreation/ScopeStepConverter.jsx index 36332b30b..7eef4c7a7 100644 --- a/DashAI/front/src/components/notebooks/converterCreation/ScopeStepConverter.jsx +++ b/DashAI/front/src/components/notebooks/converterCreation/ScopeStepConverter.jsx @@ -131,22 +131,14 @@ export default function ScopeStepConverter({ sx={{ flexShrink: 0, display: "flex", - justifyContent: "flex-end", - gap: 2, - pt: 1, + alignItems: "center", + gap: 1, + mt: "auto", + pt: 2, + borderTop: 1, + borderColor: "divider", }} > - {supervised && ( - - - - - - )} - {supervised && ( { @@ -162,19 +154,33 @@ export default function ScopeStepConverter({ notebook={notebook} /> )} - + {supervised && ( + + + + + + )} - {/* Buttons */} - 0 - ? t("common:next") - : t("common:save") - } - data-tour="converter-scope-next-button" - /> + + 0 + ? t("common:next") + : t("common:save") + } + data-tour="converter-scope-next-button" + sx={{ borderTop: 0, pt: 0, mt: 0 }} + /> + + ); } diff --git a/DashAI/front/src/components/notebooks/dataset/DatasetTable.jsx b/DashAI/front/src/components/notebooks/dataset/DatasetTable.jsx index 3d19c4919..67b38746d 100644 --- a/DashAI/front/src/components/notebooks/dataset/DatasetTable.jsx +++ b/DashAI/front/src/components/notebooks/dataset/DatasetTable.jsx @@ -22,6 +22,15 @@ export default function DatasetTable({ showExportButton = true, enableTopToolbar = true, enableRowsPerPageSelector = true, + rowActions = null, + targetColumn = null, + editableRows = [], + infiniteScroll = false, + loadMoreStep = 25, + extraActions = null, + enableRowSelection = false, + selectedRowIndices = null, + onRowSelectionChange = null, }) { return ( ); } @@ -54,4 +72,13 @@ DatasetTable.propTypes = { showExportButton: PropTypes.bool, enableTopToolbar: PropTypes.bool, enableRowsPerPageSelector: PropTypes.bool, + rowActions: PropTypes.func, + targetColumn: PropTypes.string, + editableRows: PropTypes.array, + infiniteScroll: PropTypes.bool, + loadMoreStep: PropTypes.number, + extraActions: PropTypes.node, + enableRowSelection: PropTypes.bool, + selectedRowIndices: PropTypes.instanceOf(Set), + onRowSelectionChange: PropTypes.func, }; diff --git a/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx b/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx index e6b67419b..32a4a097b 100644 --- a/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx +++ b/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx @@ -34,6 +34,7 @@ export function SaveDatasetModal({ appliedConverters, existingDatasets = [], notebook, + hasNoColumns = false, }) { const [name, setName] = useState(""); const [frozenDefaultName, setFrozenDefaultName] = useState(""); @@ -239,15 +240,15 @@ export function SaveDatasetModal({ - {/* Scrollable Content */} + {/* Static fields — always visible, never scroll */} @@ -278,30 +279,49 @@ export function SaveDatasetModal({ - - - {t("datasets:label.appliedTransformations")} + + {t("datasets:label.appliedTransformations")} + + + + {/* Scrollable converter list */} + + {localConverters.length === 0 ? ( + + {t("datasets:label.noTransformationsApplied")} - {localConverters.length === 0 ? ( - - {t("datasets:label.noTransformationsApplied")} - - ) : ( - - )} - + ) : ( + + )} {/* Footer - always visible */} + {hasNoColumns && ( + + {t("datasets:error.cannotSaveEmptyDataset")} + + )} + typeof color === "string" && + (color === "transparent" || + /^rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*0(\.0+)?\s*\)$/.test( + color, + )); export default function DebouncedColorPicker({ label, @@ -7,8 +19,15 @@ export default function DebouncedColorPicker({ onChange, delay = 300, }) { + const { t } = useTranslation(["datasets", "common"]); const [localValue, setLocalValue] = useState(value || "#000000"); const timeoutRef = useRef(null); + const transparent = isTransparentColor(value); + // Remember the last non-transparent color so unchecking "Transparent" + // restores it instead of falling back to black. + const lastColorRef = useRef( + value && !isTransparentColor(value) ? value : "#000000", + ); // Helper to expand 3-digit hex to 6-digit const expandHex = (hex) => { @@ -80,6 +99,9 @@ export default function DebouncedColorPicker({ useEffect(() => { setLocalValue(value || "#000000"); + if (value && !isTransparentColor(value)) { + lastColorRef.current = value; + } }, [value]); const handleColorChange = (e) => { @@ -116,33 +138,72 @@ export default function DebouncedColorPicker({ } }; + const handleTransparentToggle = (e) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + if (e.target.checked) { + onChange(TRANSPARENT); + } else { + // Leaving transparent: restore the color that was set before. + const restored = lastColorRef.current || "#000000"; + setLocalValue(restored); + onChange(restored); + } + }; + return ( - - + + + + + } + label={t("datasets:label.transparent", "Transparent")} + sx={{ + m: 0, + mt: 1.5, + "& .MuiFormControlLabel-label": { fontSize: "0.75rem" }, + }} /> ); diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx index c20f4a972..cb16b741b 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx @@ -196,7 +196,23 @@ const PlotLayoutForm = memo(function PlotLayoutForm({ {/* Active section content */} - + {validSection === "general" && ( )} diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx index c4f784cfa..dffbd461b 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx @@ -7,7 +7,7 @@ export default function DimensionsForm({ data, handleTraceChange }) { const theme = useTheme(); return ( - + {data[0].dimensions.map((dim, idx) => ( + {/* Title */} {t("datasets:label.title", "Title")} @@ -48,7 +48,7 @@ export default function GeneralForm({ layout, handleChange }) { fullWidth /> - + {t("datasets:label.margins")} - + - + + {/* Visibility & Orientation */} {t("datasets:label.position")} - + + (Array.isArray(colorway) && colorway[i % colorway.length]) || "#1f77b4"; + + // Colors live in different fields per trace type: waterfall keeps them under + // increasing/decreasing/totals.marker.color, line-mode scatters under + // line.color, everything else under marker.color. + const type = trace.type || "scatter"; + const isScatter = type === "scatter" || type === "scattergl"; + const mode = trace.mode || ""; + const showLine = isScatter && (mode === "" || mode.includes("lines")); + const showMarker = !isScatter || mode === "" || mode.includes("markers"); + // px.imshow() (correlation matrix, density heatmap) sets coloraxis: "coloraxis" - // on the trace, so the colorbar lives in layout.coloraxis.colorbar — not trace.colorbar. + // on the trace, so the colorbar lives in layout.coloraxis.colorbar, not trace.colorbar. // Other heatmap types that don't reference a shared coloraxis keep their colorbar // directly on the trace. const colorbarInLayout = Boolean(trace.coloraxis); @@ -51,22 +67,58 @@ export default function TraceForm({ fullWidth /> - {/* --- Scatter Plot Options --- */} - {!usesColormap(trace) && ( + {/* --- Waterfall Options (e.g. SHAP force plots): two/three colors --- */} + {type === "waterfall" && ( <> + handleTraceChange(index, "increasing.marker.color", color) } + /> + - handleTraceChange(index, "marker.color", color) + handleTraceChange(index, "decreasing.marker.color", color) } /> + + handleTraceChange(index, "totals.marker.color", color) + } + /> + + )} + + {/* --- Line / Marker Options --- */} + {type !== "waterfall" && !usesColormap(trace) && ( + <> + {showLine && ( + + handleTraceChange(index, "line.color", color) + } + /> + )} + {showMarker && ( + + handleTraceChange(index, "marker.color", color) + } + /> + )} )} diff --git a/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx b/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx index 18ffcf527..39c221244 100644 --- a/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx +++ b/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx @@ -1,6 +1,6 @@ import React from "react"; import { Box, CircularProgress } from "@mui/material"; -import { visualizersKeys } from "../useExplorerResults"; +import { visualizersKeys } from "../../../../utils/artifactVisualizerData"; import ImageVisualizer from "../visualizations/ImageVisualizer"; import PlotlyJsonVisualizer from "../visualizations/PlotlyJsonVisualizer"; import TabularVisualizer from "../visualizations/TabularVisualizer"; diff --git a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx index 6a7d9902d..8f6732cd6 100644 --- a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx +++ b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx @@ -1,55 +1,6 @@ -import { Box, Typography, Tooltip } from "@mui/material"; import { useState, useEffect } from "react"; import { getExplorerResults } from "../../../api/explorer"; -import ImageVisualizer from ".//visualizations/ImageVisualizer"; -import PlotlyJsonVisualizer from "./visualizations/PlotlyJsonVisualizer"; -import TabularVisualizer from "./visualizations/TabularVisualizer"; -import { getExplorerStatus } from "../../../utils/explorerStatus"; -import { useTranslation } from "react-i18next"; - -/** - * NullCell component to render null values in the tabular visualizer - * @param {Object} props - */ -function NullCell({}) { - const [hover, setHover] = useState(false); - const { t } = useTranslation(["common"]); - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? t("common:none") : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; - -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; +import { artifactToVisualizerData } from "../../../utils/artifactVisualizerData"; /** * Hook to manage explorer results data @@ -69,36 +20,15 @@ export function useExplorerResults(explorer) { setLoading(true); try { - const results = await getExplorerResults(explorer.id); - if (!results?.type) { - throw new Error("No result type specified in the response"); + const artifacts = await getExplorerResults(explorer.id); + const [artifact] = artifacts ?? []; + if (!artifact?.type) { + throw new Error("No artifacts in the response"); } - // Check if there is an appropriate visualizer - if (!Object.keys(visualizers).includes(results.type)) { - throw new Error(`No visualizer found for type: ${results.type}`); - } - - setDataType(results.type); - - // Process data based on type - if (results.type === visualizersKeys.tabular) { - const processedData = getDataFromOrientation( - results.data, - results.config.orient, - ); - setData({ - columns: processedData.columns, - rows: processedData.rows, - }); - } else if (results.type === visualizersKeys.plotly_json) { - setData(JSON.parse(results.data)); - } else if ( - results.type === visualizersKeys.image_base64 || - results.type === visualizersKeys.image_url - ) { - setData(results.data); - } + const visualizerData = artifactToVisualizerData(artifact); + setDataType(visualizerData.dataType); + setData(visualizerData.data); } catch (error) { console.error("Error fetching explorer results:", error); throw error; @@ -120,95 +50,3 @@ export function useExplorerResults(explorer) { fetchExplorerResults, }; } - -/** - * Get the data from the orientation given. This function is used to transform the data - * from the explorer results to the format required by the tabular visualizer. - * @param {Object} data The data from the explorer results - * @param {String} orientation The orientation of the data - */ -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - // ‘dict’ (default) : dict like {column -> {index -> value}} - // Get the columns - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => { - return ( - - {params.value} - - ); - }, - }, - ...columns.map((column) => { - return { - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) { - return ; - } else if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } else if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const tooltip = params.value; - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - const tooltip = params.value; - return ( - - {params.value} - - ); - }, - }; - }), - ]; - - // Get the rows - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { - id: index, - }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; - -export { visualizersKeys }; diff --git a/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx b/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx index b196fff97..1fca87070 100644 --- a/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx +++ b/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx @@ -2,7 +2,7 @@ import React, { useState, useRef } from "react"; import PropTypes from "prop-types"; import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; -import { Box, IconButton, Tooltip } from "@mui/material"; +import { Box, IconButton, Tooltip, Menu, MenuItem } from "@mui/material"; import Dialog from "@mui/material/Dialog"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; import ZoomOutIcon from "@mui/icons-material/ZoomOut"; @@ -15,9 +15,15 @@ const MIN_WIDTH = 300; const MIN_HEIGHT_MINIMALIST = 200; const MIN_HEIGHT_NORMAL = 500; -function PlotlyJsonVisualizer({ data, minimalist = false }) { +function PlotlyJsonVisualizer({ + data, + minimalist = false, + fillHeight = false, +}) { const theme = useTheme(); const [expanded, setExpanded] = useState(false); + // Which plot ref + anchor the download format menu (PNG/SVG) applies to. + const [downloadMenu, setDownloadMenu] = useState(null); const plotRef = useRef(null); const fullscreenPlotRef = useRef(null); @@ -40,10 +46,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { title: "", }, } - : { - ...parsedData, - layout: { ...parsedData.layout, height: MIN_HEIGHT_NORMAL }, - }; + : fillHeight + ? { ...parsedData, layout: { ...parsedData.layout } } + : { + ...parsedData, + layout: { ...parsedData.layout, height: MIN_HEIGHT_NORMAL }, + }; const getPlotly = (ref) => { const el = ref?.current?.el; @@ -103,12 +111,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { }); }; - const handleDownload = (ref) => { + const handleDownload = (ref, format = "svg") => { const el = ref?.current?.el; const Plotly = getPlotly(ref); if (el && Plotly) { Plotly.downloadImage(el, { - format: "svg", + format, filename: "dashai-plot", height: 800, width: 1200, @@ -246,10 +254,16 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { )} - + handleDownload(plotRefProp)} + onClick={(e) => + setDownloadMenu({ + mouseX: e.clientX, + mouseY: e.clientY, + ref: plotRefProp, + }) + } sx={downloadBtnSx} > @@ -265,8 +279,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { position: "relative", width: "100%", minWidth: MIN_WIDTH, - minHeight: minimalist ? MIN_HEIGHT_MINIMALIST : MIN_HEIGHT_NORMAL, - height: minimalist ? "100%" : "auto", + minHeight: fillHeight + ? 0 + : minimalist + ? MIN_HEIGHT_MINIMALIST + : MIN_HEIGHT_NORMAL, + height: minimalist || fillHeight ? "100%" : "auto", overflow: "hidden", display: "flex", justifyContent: "center", @@ -283,8 +301,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { revision={revisionRef.current} style={{ width: "100%", - minHeight: minimalist ? MIN_HEIGHT_MINIMALIST : MIN_HEIGHT_NORMAL, - height: minimalist ? "100%" : MIN_HEIGHT_NORMAL, + minHeight: fillHeight + ? 0 + : minimalist + ? MIN_HEIGHT_MINIMALIST + : MIN_HEIGHT_NORMAL, + height: minimalist || fillHeight ? "100%" : MIN_HEIGHT_NORMAL, }} config={plotConfig} useResizeHandler={true} @@ -340,6 +362,34 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { )} + + setDownloadMenu(null)} + anchorReference="anchorPosition" + anchorPosition={ + downloadMenu + ? { top: downloadMenu.mouseY, left: downloadMenu.mouseX } + : undefined + } + > + { + handleDownload(downloadMenu.ref, "png"); + setDownloadMenu(null); + }} + > + PNG + + { + handleDownload(downloadMenu.ref, "svg"); + setDownloadMenu(null); + }} + > + SVG + + ); } @@ -347,6 +397,7 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { PlotlyJsonVisualizer.propTypes = { data: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired, minimalist: PropTypes.bool, + fillHeight: PropTypes.bool, }; export default PlotlyJsonVisualizer; diff --git a/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx b/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx index dc62344a3..c4d0e2fa6 100644 --- a/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx +++ b/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx @@ -63,6 +63,7 @@ export default function DatasetPreviewNotebook({ ); const [localColumnTypes, setLocalColumnTypes] = useState({}); + const [totalRows, setTotalRows] = useState(null); // Sync types from context — updated by fetchExplorersAndConverters (initial // load + delete path) and by handleStatusChange / FormConverterSection @@ -97,6 +98,14 @@ export default function DatasetPreviewNotebook({ [notebook?.id], ); + // Re-fetch row count whenever converters change + useEffect(() => { + if (!notebook?.file_path) return; + fetchDatasetPage(0, 1, null, null) + .then(({ total }) => setTotalRows(total)) + .catch(() => setTotalRows(null)); + }, [converterKey, fetchDatasetPage]); + if (!notebook) { return ( + + {t("common:description")} + + + {notebookData.description} + + + ); + return ( - - - {/* Modal Header */} - - - - {t("datasets:label.notebookInformation")} - - - {notebookData.name} - - - - - - - - - {/* Description Section - Only show if description exists */} - {notebookData.description && notebookData.description.trim() && ( - - - {t("common:description")} - - - {notebookData.description} - - - )} - - - {t("common:metadata")} - - - - - - - {t("common:id")} - - {notebookData.id} - - - - {t("datasets:label.associatedDataset")} - - {getDatasetName()} - - - - {t("common:created")} - - - {formatDate(notebookData.created)} - - - - - {t("common:lastModified")} - - - {formatDate(notebookData.last_modified)} - - - -
-
-
-
-
+ /> ); } diff --git a/DashAI/front/src/components/notebooks/notebookCreation/DatasetAutocomplete.jsx b/DashAI/front/src/components/notebooks/notebookCreation/DatasetAutocomplete.jsx index 62849cc4f..293a59ba2 100644 --- a/DashAI/front/src/components/notebooks/notebookCreation/DatasetAutocomplete.jsx +++ b/DashAI/front/src/components/notebooks/notebookCreation/DatasetAutocomplete.jsx @@ -16,6 +16,7 @@ export default function DatasetAutocomplete({ datasets, selectedDataset, setSelectedDataset, + showDetails = true, }) { const { t } = useTranslation(["datasets", "common"]); @@ -87,7 +88,7 @@ export default function DatasetAutocomplete({ sx={{ mb: 6 }} /> - {selectedDataset && ( + {showDetails && selectedDataset && ( setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? "None" : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; - -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; - -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => ( - - {params.value} - - ), - }, - ...columns.map((column) => ({ - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) return ; - if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } - if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - return ( - - {params.value} - - ); - }, - })), - ]; - - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { id: index }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; +import { + artifactToVisualizerData, + visualizersKeys, +} from "../../../utils/artifactVisualizerData"; function Results({ pipelineId }) { const [explorationResults, setExplorationResults] = useState(null); @@ -137,36 +29,31 @@ function Results({ pipelineId }) { } }, [pipelineId]); - const renderVisualizer = (type, dataObj) => { - if (!Object.keys(visualizers).includes(type)) { - console.error(`No visualizer found for type: ${type}`); + const renderArtifact = (artifact, key) => { + let visualizerData; + try { + visualizerData = artifactToVisualizerData(artifact); + } catch (error) { + console.error(error); return null; } + const { dataType, data } = visualizerData; - if (type === visualizersKeys.tabular) { - const data = getDataFromOrientation(dataObj.data, dataObj.config.orient); + if (dataType === visualizersKeys.tabular) { return ( - + ); } - if (type === visualizersKeys.plotly_json) { - return ( - - ); - } - - if (type === visualizersKeys.image_base64) { - return ( - - ); + if (dataType === visualizersKeys.plotly_json) { + return ; } - if (type === visualizersKeys.image_url) { - return ; + if ( + dataType === visualizersKeys.image_base64 || + dataType === visualizersKeys.image_url + ) { + return ; } return null; @@ -193,7 +80,9 @@ function Results({ pipelineId }) { {i}: {result.exploration_type} {result.name ? ` | ${result.name}` : ""} - {renderVisualizer(result.results.type, result.results)} + {(result.results ?? []).map((artifact, artifactIndex) => + renderArtifact(artifact, `${explorationName}-${artifactIndex}`), + )}
), )} diff --git a/DashAI/front/src/components/predictions/DatasetSelector.jsx b/DashAI/front/src/components/predictions/DatasetSelector.jsx index 5790fd97d..1c152f500 100644 --- a/DashAI/front/src/components/predictions/DatasetSelector.jsx +++ b/DashAI/front/src/components/predictions/DatasetSelector.jsx @@ -13,7 +13,6 @@ import { getDatasetFile, getDatasetFileFiltered, getDatasetTypesByFilePath, - getDatasetInfo, } from "../../api/datasets"; import { formatDate } from "../../pages/results/constants/formatDate"; import { useTranslation } from "react-i18next"; @@ -26,7 +25,6 @@ function DatasetSelector({ }) { const { t } = useTranslation(["prediction", "common", "datasets"]); const [columnTypes, setColumnTypes] = useState({}); - const [infosById, setInfosById] = useState({}); useEffect(() => { if (!selectedDataset?.file_path) return; @@ -35,24 +33,6 @@ function DatasetSelector({ .catch(() => {}); }, [selectedDataset?.file_path]); - const fetchMissingInfos = async (items) => { - const missing = items.filter((d) => d?.id != null && !infosById[d.id]); - if (missing.length === 0) return; - try { - const results = await Promise.allSettled( - missing.map((d) => getDatasetInfo(d.id)), - ); - const map = {}; - results.forEach((res, idx) => { - const id = missing[idx]?.id; - if (res.status === "fulfilled" && id != null) map[id] = res.value; - }); - setInfosById((prev) => ({ ...prev, ...map })); - } catch (e) { - console.warn("Some dataset infos could not be fetched", e); - } - }; - const fetchDatasetPage = useCallback( async (page, pageSize, filterModel, sortModel) => { const hasFilters = @@ -78,7 +58,6 @@ function DatasetSelector({ getOptionLabel={(option) => option.name} isOptionEqualToValue={(opt, val) => opt.id === val.id} value={selectedDataset} - onOpen={() => fetchMissingInfos(datasets)} onChange={(_, newValue) => setSelectedDataset(newValue)} renderInput={(params) => ( { const { key, ...rootProps } = props; - const info = infosById[option.id]; return ( {t("datasets:label.rowsColumnsInfo", { - totalRows: info ? info.total_rows : "...", - totalColumns: info ? info.total_columns : "...", + totalRows: option.total_rows ?? "...", + totalColumns: option.total_columns ?? "...", })} diff --git a/DashAI/front/src/components/predictions/ManualInput.jsx b/DashAI/front/src/components/predictions/ManualInput.jsx index 3ea53ed95..b6753f004 100644 --- a/DashAI/front/src/components/predictions/ManualInput.jsx +++ b/DashAI/front/src/components/predictions/ManualInput.jsx @@ -17,6 +17,9 @@ export default function ManualInput({ onRun = null, isPreviewing = false, isSaving = false, + showTarget = true, + title, + subtitle, }) { const { t } = useTranslation(["prediction"]); @@ -37,6 +40,9 @@ export default function ManualInput({ onRun={onRun} isPreviewing={isPreviewing} isSaving={isSaving} + showTarget={showTarget} + title={title} + subtitle={subtitle} /> ) : (
{t("prediction:label.noExperimentDataAvailable")}
diff --git a/DashAI/front/src/components/predictions/ManualInputForm.jsx b/DashAI/front/src/components/predictions/ManualInputForm.jsx index 2e8a2a4cb..1bf693068 100644 --- a/DashAI/front/src/components/predictions/ManualInputForm.jsx +++ b/DashAI/front/src/components/predictions/ManualInputForm.jsx @@ -27,6 +27,9 @@ export default function ManualInputForm({ onRun = null, isPreviewing = false, isSaving = false, + showTarget = true, + title, + subtitle, }) { const theme = useTheme(); const [rows, setRows] = useState(createInitialRows); @@ -91,12 +94,19 @@ export default function ManualInputForm({ if (onSubmit) onSubmit(rows); }; - const headerBg = - theme.palette.mode === "dark" - ? "rgba(255,255,255,0.05)" - : "rgba(0,0,0,0.02)"; + // Match the lean dataset table's look (see leanDatasetTable.css): a panelDark + // surface, hairline gray borders, 13px text, a sticky header, and the blue + // accent used for its pinned/target column. + const headerBg = theme.palette.ui.panelDark; + const bodyBg = theme.palette.ui.panelDark; + const containerBorder = "rgba(128, 128, 128, 0.3)"; + const headerBorder = "rgba(128, 128, 128, 0.4)"; + const accent = "rgb(100, 150, 255)"; + const targetHeaderBg = `linear-gradient(rgba(100, 150, 255, 0.16), rgba(100, 150, 255, 0.16)), ${headerBg}`; + const targetCellBg = `linear-gradient(rgba(100, 150, 255, 0.08), rgba(100, 150, 255, 0.08)), ${bodyBg}`; - const divider = theme.palette.divider; + // Hairline used for internal cell and column borders (lean's cell border). + const divider = "rgba(128, 128, 128, 0.15)"; const textPrimary = theme.palette.text.primary; const textSecondary = theme.palette.text.secondary; @@ -104,25 +114,29 @@ export default function ManualInputForm({ ? predictionResults.columns[predictionResults.columns.length - 1] : targetColumn; - // Shared plain- styles - no Emotion per-cell cost. + // Plain td styles, kept inline to avoid the per cell Emotion cost. const thStyle = { - padding: "8px 12px", + padding: "6px 10px", whiteSpace: "nowrap", minWidth: 120, fontWeight: 600, - fontSize: "0.875rem", + fontSize: 13, color: textPrimary, height: HEADER_HEIGHT, background: headerBg, - borderBottom: `2px solid ${divider}`, + borderBottom: `1px solid ${headerBorder}`, verticalAlign: "middle", textAlign: "left", + position: "sticky", + top: 0, + zIndex: 1, }; const tdStyle = { - padding: "6px 12px", + padding: "4px 10px", whiteSpace: "nowrap", minWidth: 120, + fontSize: 13, color: textPrimary, height: ROW_HEIGHT, borderBottom: `1px solid ${divider}`, @@ -142,10 +156,10 @@ export default function ManualInputForm({ onSubmit={handleSubmit} > - {t("prediction:label.manualInputData")} + {title ?? t("prediction:label.manualInputData")} - {t("prediction:label.provideManualInput")} + {subtitle ?? t("prediction:label.provideManualInput")} @@ -181,10 +195,10 @@ export default function ManualInputForm({ {/* Scrollable input columns */} @@ -240,26 +254,33 @@ export default function ManualInputForm({ + {showTarget && ( + + )} - + {showTarget && ( + + )} ; + return ( + + ); } const text = value == null ? "" : String(value); return ( - ); }); -LeanCell.propTypes = { value: PropTypes.any, query: PropTypes.string }; +LeanCell.propTypes = { + value: PropTypes.any, + query: PropTypes.string, + isPinned: PropTypes.bool, + pinnedOffset: PropTypes.number, +}; export default LeanCell; diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx index 292d41ad4..b3a5f4acb 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import PropTypes from "prop-types"; -import { Box, TablePagination, Typography } from "@mui/material"; +import { Box, Checkbox, TablePagination, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useSnackbar } from "notistack"; import { useTranslation } from "react-i18next"; @@ -15,6 +15,11 @@ import { defaultOpForType, toBackendOperator } from "./operators"; import "./leanDatasetTable.css"; +// Matches the fixed width set on .lean-th--actions / .lean-cell--actions in +// leanDatasetTable.css — used to offset the pinned target column so it sits +// flush against the actions column instead of underneath it. +const ACTIONS_COLUMN_WIDTH = 70; + /** * Lightweight dataset preview table. Renders a native ``
+ {targetLabel ?? ""} + - {targetLabel ?? ""} - - - {predVal != null - ? typeof predVal === "number" - ? targetDecimals !== null - ? predVal.toFixed(targetDecimals) - : String(parseFloat(predVal.toPrecision(12))) - : String(predVal) - : ""} - + {predVal != null + ? typeof predVal === "number" + ? targetDecimals !== null + ? predVal.toFixed(targetDecimals) + : String(parseFloat(predVal.toPrecision(12))) + : String(predVal) + : ""} + { if (run) { try { - const availableDatasets = await filterDatasets({ run_id: run.id }); - const availableDatasetsWithInfo = await Promise.all( - availableDatasets.map(async (dataset) => { - // Fetch additional info about the datasets - const datasetInfo = await getDatasetInfo(dataset.id); - return { ...dataset, ...datasetInfo }; - }), + const [allDatasets, validIds] = await Promise.all([ + getDatasets(), + filterDatasets({ run_id: run.id }), + ]); + const validIdSet = new Set(validIds.map(String)); + setDatasets( + allDatasets.filter((ds) => validIdSet.has(String(ds.id))), ); - - setDatasets(availableDatasetsWithInfo); } catch (error) { console.error("Error fetching datasets:", error); enqueueSnackbar(t("prediction:error.fetchingDatasets"), { diff --git a/DashAI/front/src/components/shared/ArtifactRenderer.jsx b/DashAI/front/src/components/shared/ArtifactRenderer.jsx new file mode 100644 index 000000000..6bd20fbf6 --- /dev/null +++ b/DashAI/front/src/components/shared/ArtifactRenderer.jsx @@ -0,0 +1,121 @@ +import React, { useMemo } from "react"; +import { Box, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import Plot from "react-plotly.js"; +import PropTypes from "prop-types"; +import { useTranslation } from "react-i18next"; + +import { applyThemeToLayout } from "../../utils/plotlyTheme"; +import TableArtifact from "./TableArtifact"; + +/** + * Renders a single typed artifact ({type, payload, title}) returned by the + * backend (explainer plots, explorer results). Supported types: "plotly" + * (payload: plotly JSON string), "table" (payload: {columns, rows, + * highlight}), "image" (payload: {data, mime}) and "text" (payload: string). + * Unknown types fall back to preformatted text so nothing is silently lost. + * The optional height sets the plot height and caps image/table height; it + * lets callers render larger (for example a fullscreen view). + */ +export default function ArtifactRenderer({ artifact, height = 380 }) { + const theme = useTheme(); + const { t } = useTranslation(["common"]); + + const parsedFigure = useMemo(() => { + if (artifact.type !== "plotly") return null; + try { + return typeof artifact.payload === "string" + ? JSON.parse(artifact.payload) + : artifact.payload; + } catch (error) { + console.error("Invalid plotly artifact payload", error); + return null; + } + }, [artifact]); + + const themedLayout = useMemo(() => { + if (!parsedFigure) return {}; + // User-edited (overridden) figures are rendered verbatim so their saved + // colors/background survive; only strip fixed sizing. Non-overridden + // figures follow the app light/dark theme. + if (artifact.overridden) { + const { width: _w, height: _h, ...rest } = parsedFigure.layout ?? {}; + return rest; + } + return applyThemeToLayout(parsedFigure.layout, theme); + }, [parsedFigure, theme, artifact.overridden]); + + const highlightedCells = useMemo(() => { + if (artifact.type !== "table") return new Set(); + const cells = artifact.payload?.highlight ?? []; + return new Set(cells.map((cell) => `${cell.row}-${cell.column}`)); + }, [artifact]); + + const renderContent = () => { + switch (artifact.type) { + case "plotly": + if (!parsedFigure) return null; + return ( + + ); + case "table": { + const { columns = [], rows = [] } = artifact.payload ?? {}; + return ( + + ); + } + case "image": { + const { data = "", mime = "image/png" } = artifact.payload ?? {}; + return ( + + ); + } + case "text": + default: + return ( + + {typeof artifact.payload === "string" + ? artifact.payload + : JSON.stringify(artifact.payload, null, 2)} + + ); + } + }; + + return ( + + {artifact.title && ( + + {artifact.title} + + )} + {renderContent()} + + ); +} + +ArtifactRenderer.propTypes = { + artifact: PropTypes.shape({ + type: PropTypes.string.isRequired, + payload: PropTypes.any, + title: PropTypes.string, + overridden: PropTypes.bool, + }).isRequired, + height: PropTypes.number, +}; diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx new file mode 100644 index 000000000..cbff74873 --- /dev/null +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -0,0 +1,470 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import PropTypes from "prop-types"; +import { + Box, + IconButton, + Menu, + MenuItem, + Typography, + Tooltip, + Dialog, + Divider, +} from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; +import EditIcon from "@mui/icons-material/Edit"; +import FullscreenIcon from "@mui/icons-material/Fullscreen"; +import RestartAltIcon from "@mui/icons-material/RestartAlt"; +import CloseIcon from "@mui/icons-material/Close"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import { useTheme, alpha } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; + +import ArtifactRenderer from "./ArtifactRenderer"; +import PlotLayoutForm from "../notebooks/explorer/plotLayout/PlotLayoutForm"; +import PlotlyJsonVisualizer from "../notebooks/explorer/visualizations/PlotlyJsonVisualizer"; +import { applyThemeToLayout } from "../../utils/plotlyTheme"; +import { downloadArtifact } from "../../utils/downloadArtifact"; + +/** + * Renders one typed artifact as a self contained bordered block. The actions + * that apply to that artifact (download, plot editing, fullscreen) live in a + * compact cluster docked to the block's top right corner, revealed on hover + * or keyboard focus so the resting card stays uncluttered. Plot editing is + * offered only for plotly artifacts; edits persist when onSaveEdit is given, + * otherwise they are client side only. + */ +export default function ArtifactViewer({ + artifact, + onSaveEdit = null, + onResetEdit = null, + canReset = false, + siblingArtifacts = null, + siblingIndex = 0, +}) { + const theme = useTheme(); + const { t } = useTranslation(["explainers", "common"]); + const [downloadAnchor, setDownloadAnchor] = useState(null); + const [editing, setEditing] = useState(false); + const [fullscreen, setFullscreen] = useState(false); + const [fullscreenIndex, setFullscreenIndex] = useState(siblingIndex); + const hasSiblings = siblingArtifacts && siblingArtifacts.length > 1; + + // The parent list does not refetch after a save, so hold the edited payload + // locally and render from it, making a save show immediately. Cleared when + // the underlying artifact prop actually changes (e.g. a real refetch/reset). + const [localPayload, setLocalPayload] = useState(null); + useEffect(() => { + setLocalPayload(null); + }, [artifact.payload]); + const shownArtifact = + localPayload != null + ? { ...artifact, payload: localPayload, overridden: true } + : artifact; + + const fullscreenArtifact = hasSiblings + ? siblingArtifacts[fullscreenIndex] + : shownArtifact; + + const openFullscreen = () => { + setFullscreenIndex(siblingIndex); + setFullscreen(true); + }; + + const stepFullscreen = (delta) => { + if (!hasSiblings) return; + setFullscreenIndex( + (fullscreenIndex + delta + siblingArtifacts.length) % + siblingArtifacts.length, + ); + }; + // Working copies the form editor mutates. The preview Plot renders from these + // directly, so edits made in the form show live; the form is the single + // source of truth, so there is no Plotly edit-event feedback loop. + const [editData, setEditData] = useState(null); + const [editLayout, setEditLayout] = useState(null); + const plotWrapRef = useRef(null); + const isPlotly = artifact.type === "plotly"; + + const figure = useMemo(() => { + if (!isPlotly) return null; + try { + return typeof shownArtifact.payload === "string" + ? JSON.parse(shownArtifact.payload) + : shownArtifact.payload; + } catch (error) { + console.error("Invalid plotly payload", error); + return null; + } + }, [shownArtifact, isPlotly]); + + const startEdit = () => { + if (!figure?.data) return; + setEditData(structuredClone(figure.data)); + // An already-overridden figure keeps its saved colors; a fresh one is + // themed so the editor starts from the current on-screen appearance. + setEditLayout( + shownArtifact.overridden + ? structuredClone(figure.layout ?? {}) + : applyThemeToLayout(figure.layout, theme), + ); + setEditing(true); + }; + + const closeEdit = () => { + setEditing(false); + setEditData(null); + setEditLayout(null); + }; + + const saveEdit = async () => { + try { + const edited = { data: editData, layout: editLayout }; + if (onSaveEdit && editData) { + await onSaveEdit(edited); + } + // Reflect the save immediately, since the parent list is not refetched. + setLocalPayload(JSON.stringify(edited)); + closeEdit(); + } catch (error) { + console.error("Failed to save plot edits", error); + } + }; + + const findPlotEl = () => + plotWrapRef.current + ? plotWrapRef.current.querySelector(".js-plotly-plot") + : null; + + const actionButtonSx = { + color: "text.secondary", + "&:hover": { color: "text.primary" }, + }; + + // Circular "glass" buttons for the fullscreen lightbox: translucent white + // against the dark blurred overlay, regardless of the app's light/dark + // theme (the overlay itself is always near black). + const lightboxButtonSx = { + position: "absolute", + top: 16, + right: 16, + zIndex: 1, + width: 36, + height: 36, + color: "#fff", + bgcolor: "rgba(255, 255, 255, 0.12)", + border: "1px solid rgba(255, 255, 255, 0.2)", + "&:hover": { bgcolor: "rgba(255, 255, 255, 0.25)" }, + }; + + const lightboxArrowSx = { + top: "50%", + right: "auto", + transform: "translateY(-50%)", + width: 44, + height: 44, + }; + + // Fill 75% of the viewport in the fullscreen view, minus the card's vertical + // padding (p: 4 => 32px top + 32px bottom) so the content fits within the + // 75vh card without producing a scrollbar. + const fullscreenHeight = + typeof window !== "undefined" + ? Math.max(360, Math.round(window.innerHeight * 0.75) - 64) + : 720; + + return ( + + {/* Action cluster, docked to the block corner and attached to this + artifact's content. */} + + + + isPlotly + ? setDownloadAnchor(e.currentTarget) + : downloadArtifact(artifact) + } + > + + + + {isPlotly && figure && ( + + + + + + )} + {canReset && onResetEdit && ( + + { + setLocalPayload(null); + onResetEdit(); + }} + > + + + + )} + + + + + + + + setDownloadAnchor(null)} + > + { + downloadArtifact(artifact, { plotEl: findPlotEl(), format: "png" }); + setDownloadAnchor(null); + }} + > + {t("explainers:button.downloadPng")} + + { + downloadArtifact(artifact, { plotEl: findPlotEl(), format: "svg" }); + setDownloadAnchor(null); + }} + > + {t("explainers:button.downloadSvg")} + + + + {/* The instance label is shown once by the parent; suppress the + per artifact title so it is not repeated on every block. */} + + + {/* Edit dialog: a live plot preview beside the shared form layout + editor (reused from the explorer view). The form mutates editData / + editLayout and the preview renders from them, so edits show live. */} + + + + {t("explainers:button.editPlot")} + + + + + + + + {/* Live preview: reuses the explorer's plot viewer so it shows the + same overlay icon buttons (zoom, reset, fullscreen, download). */} + + {editData && ( + + )} + + {/* Form layout/trace editor */} + + {editData && ( + + )} + + + + + {/* Fullscreen view: dark blurred lightbox overlay, rounded/shadowed + content card, and circular glass buttons for close + prev/next. */} + setFullscreen(false)} + transitionDuration={0} + keepMounted + PaperProps={{ + elevation: 0, + sx: { + bgcolor: "rgba(0, 0, 0, 0.15)", + backgroundImage: "none", + backdropFilter: "blur(6px)", + willChange: "backdrop-filter", + boxShadow: "none", + }, + }} + > + { + if (e.target === e.currentTarget) setFullscreen(false); + }} + sx={{ + position: "relative", + flex: 1, + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + setFullscreen(false)} + aria-label={t("explainers:button.close", { + defaultValue: "Close", + })} + sx={lightboxButtonSx} + > + + + + {hasSiblings && ( + stepFullscreen(-1)} + aria-label="previous" + sx={{ ...lightboxButtonSx, ...lightboxArrowSx, left: 20 }} + > + + + )} + + + + + + {hasSiblings && ( + stepFullscreen(1)} + aria-label="next" + sx={{ ...lightboxButtonSx, ...lightboxArrowSx, right: 20 }} + > + + + )} + + + + ); +} + +ArtifactViewer.propTypes = { + artifact: PropTypes.shape({ + type: PropTypes.string.isRequired, + payload: PropTypes.any, + title: PropTypes.string, + role: PropTypes.string, + }).isRequired, + onSaveEdit: PropTypes.func, + onResetEdit: PropTypes.func, + canReset: PropTypes.bool, + siblingArtifacts: PropTypes.array, + siblingIndex: PropTypes.number, +}; diff --git a/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx b/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx index c6b608302..f0249a24b 100644 --- a/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx +++ b/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx @@ -1,22 +1,40 @@ import React from "react"; +import PropTypes from "prop-types"; import Breadcrumbs from "@mui/material/Breadcrumbs"; import Typography from "@mui/material/Typography"; import Link from "@mui/material/Link"; import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; import { useFormSchemaStore } from "../../contexts/schema"; /** * This component is the breadcrumbs for the form schema + * @param {string} rootLabel - Label for the root (top level) model crumb */ -function FormSchemaBreadScrumbs() { +function FormSchemaBreadScrumbs({ rootLabel }) { const theme = useTheme(); + const { t } = useTranslation(["common"]); const { properties, removeLastProperty } = useFormSchemaStore(); const handleRemoveLastProperty = (index) => { removeLastProperty(properties.length - 1 - index); }; + // Root crumb: pops every nested property to return to the top level model. + const rootCrumb = ( + removeLastProperty(properties.length)} + sx={{ background: "none", border: "none", cursor: "pointer" }} + > + {rootLabel || t("common:model")} + + ); + const linkedProperties = properties .slice(0, properties.length - 1) .map((property, index) => ( @@ -33,7 +51,8 @@ function FormSchemaBreadScrumbs() { )); return ( - + + {rootCrumb} {linkedProperties} {properties[properties.length - 1]?.label} @@ -42,4 +61,8 @@ function FormSchemaBreadScrumbs() { ); } +FormSchemaBreadScrumbs.propTypes = { + rootLabel: PropTypes.string, +}; + export default FormSchemaBreadScrumbs; diff --git a/DashAI/front/src/components/shared/FormSchemaButtonGroup.jsx b/DashAI/front/src/components/shared/FormSchemaButtonGroup.jsx index ea2048256..24ea046e0 100644 --- a/DashAI/front/src/components/shared/FormSchemaButtonGroup.jsx +++ b/DashAI/front/src/components/shared/FormSchemaButtonGroup.jsx @@ -11,6 +11,7 @@ function FormSchemaButtonGroup({ saveButtonText, backButtonText, dataTour, + sx, }) { const { t } = useTranslation(["common", "datasets"]); const finalSaveText = saveButtonText ?? t("common:save"); @@ -45,6 +46,7 @@ function FormSchemaButtonGroup({ showBack={!!onCancel} showNext={!!onFormSubmit} nextDataTour={finalDataTour} + sx={sx} /> ); } @@ -58,6 +60,7 @@ FormSchemaButtonGroup.propTypes = { saveButtonText: PropTypes.string, backButtonText: PropTypes.string, dataTour: PropTypes.string, + sx: PropTypes.object, }; export default FormSchemaButtonGroup; diff --git a/DashAI/front/src/components/shared/FormSchemaFieldCard.jsx b/DashAI/front/src/components/shared/FormSchemaFieldCard.jsx index bd58a134e..7bf8dade0 100644 --- a/DashAI/front/src/components/shared/FormSchemaFieldCard.jsx +++ b/DashAI/front/src/components/shared/FormSchemaFieldCard.jsx @@ -154,7 +154,7 @@ function FormSchemaFieldCard({ {/* ── Header ── */} {(description || errorMessage) && ( - + { - setSwitchState( - field?.value?.optimize ?? paramJsonSchema?.placeholder?.optimize ?? false, - ); - }, [field?.value?.optimize, paramJsonSchema?.placeholder?.optimize]); const handleSwitchChange = () => { const toggled = !switchState; - setSwitchState(toggled); const placeholder = paramJsonSchema?.placeholder; const hasError = field?.error !== undefined; field.onChange({ diff --git a/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx b/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx index 5c35bf03c..96f23a9e4 100644 --- a/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx +++ b/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx @@ -1,4 +1,4 @@ -import { MenuItem, Tooltip, IconButton } from "@mui/material"; +import { Box, MenuItem, Tooltip, IconButton } from "@mui/material"; import PropTypes from "prop-types"; import { Input } from "../configurableObject/Inputs/InputStyles"; import React from "react"; @@ -12,6 +12,7 @@ import useModelParents from "../../hooks/useModelParents"; import { Settings } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import FormSchemaFieldCard from "./FormSchemaFieldCard"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; /** * Renders a parent-model selector field as a card. @@ -26,11 +27,15 @@ function FormSchemaFieldWithParent({ errorMessage, }) { const { addProperty, getModelFromCurrentProperty } = useFormSchemaStore(); - const { models } = useModelParents({ + const { models, markDownloaded } = useModelParents({ parent: field.value?.properties.component, }); const { t } = useTranslation(["common"]); + const selectedComponent = models?.find( + (model) => model.name === getModelFromCurrentProperty(name), + ); + const handleOnChange = async (event) => { const model = models?.find((model) => model.name === event.target.value); const { initialValues } = generateYupSchema( @@ -79,6 +84,16 @@ function FormSchemaFieldWithParent({ ))} + {selectedComponent?.metadata?.requires_download && ( + + + markDownloaded(selectedComponent.name, isDownloaded) + } + /> + + )} ); } diff --git a/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx b/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx index cb8c3fd89..ad9ab3967 100644 --- a/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx +++ b/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx @@ -1,4 +1,4 @@ -import { FormControl, MenuItem } from "@mui/material"; +import { Box, FormControl, MenuItem } from "@mui/material"; import React from "react"; import useModelParents from "../../hooks/useModelParents"; import { Input } from "../configurableObject/Inputs/InputStyles"; @@ -10,6 +10,7 @@ import { } from "../../utils/schema"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; /** * This component is a select input for the models of a parent model @@ -19,7 +20,7 @@ import { useTranslation } from "react-i18next"; */ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { - const { models } = useModelParents({ parent }); + const { models, markDownloaded } = useModelParents({ parent }); const { handleUpdateSchema } = useFormSchemaStore(); const { t } = useTranslation(["common"]); @@ -27,6 +28,10 @@ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { return null; } + const selectedComponent = models.find( + (model) => model.name === selectedModel, + ); + const handleOnChange = async (event) => { const model = models.find((model) => model.name === event.target.value); const { initialValues } = generateYupSchema( @@ -52,6 +57,16 @@ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { ))} + {selectedComponent?.metadata?.requires_download && ( + + + markDownloaded(selectedComponent.name, isDownloaded) + } + /> + + )} ); } diff --git a/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx b/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx index 67357e678..41721ec87 100644 --- a/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx +++ b/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx @@ -74,7 +74,7 @@ function FormSchemaWithSelectedModel({ > {Boolean(propertyData?.parent) && ( <> - + + + + + + {title} + + {subtitle && ( + + {subtitle} + + )} + + + + + + + + {extraContent} + + {rows.length > 0 && ( + <> + + {t("common:metadata")} + + + + + {rows.map(({ label, value }) => ( + + + {label} + + {value} + + ))} + +
+
+ + )} +
+
+ + ); +} + +InfoModal.propTypes = { + title: PropTypes.node.isRequired, + subtitle: PropTypes.node, + rows: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.node.isRequired, + value: PropTypes.node, + }), + ).isRequired, + extraContent: PropTypes.node, + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/shared/PillTabs.jsx b/DashAI/front/src/components/shared/PillTabs.jsx new file mode 100644 index 000000000..70b372457 --- /dev/null +++ b/DashAI/front/src/components/shared/PillTabs.jsx @@ -0,0 +1,66 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Tabs } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +/** + * Segmented pill-bar tabs (Results/Predictions groups in the run detail + * view, DatasetVisualization's tabs) — use this instead of a bare MUI Tabs + * for any tab group that should look the same everywhere, including the + * diagonal-stripe treatment for disabled tabs. + */ +function PillTabs({ sx, minHeight, ...props }) { + const theme = useTheme(); + + return ( + + ); +} + +PillTabs.propTypes = { + sx: PropTypes.object, + minHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), +}; + +PillTabs.defaultProps = { + sx: {}, + minHeight: 40, +}; + +export default PillTabs; diff --git a/DashAI/front/src/components/shared/PillToggleButtonGroup.jsx b/DashAI/front/src/components/shared/PillToggleButtonGroup.jsx new file mode 100644 index 000000000..ebf6edc25 --- /dev/null +++ b/DashAI/front/src/components/shared/PillToggleButtonGroup.jsx @@ -0,0 +1,54 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { ToggleButtonGroup } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; + +/** + * Segmented pill selector matching the app's tab styling (the + * Results/Predictions tabs, DatasetVisualization's tabs) — use this instead + * of a bare MUI ToggleButtonGroup for any in-page filter/split control that + * should look the same everywhere (e.g. Dataset/Manual, Train/Validation/Test). + */ +function PillToggleButtonGroup({ sx, ...props }) { + const theme = useTheme(); + + return ( + + ); +} + +PillToggleButtonGroup.propTypes = { + sx: PropTypes.object, +}; + +PillToggleButtonGroup.defaultProps = { + sx: {}, +}; + +export default PillToggleButtonGroup; diff --git a/DashAI/front/src/components/shared/PlotActions.jsx b/DashAI/front/src/components/shared/PlotActions.jsx new file mode 100644 index 000000000..e404a4eea --- /dev/null +++ b/DashAI/front/src/components/shared/PlotActions.jsx @@ -0,0 +1,188 @@ +import { useState } from "react"; +import PropTypes from "prop-types"; +import { + Box, + IconButton, + Tooltip, + Menu, + MenuItem, + Dialog, +} from "@mui/material"; +import Plot from "react-plotly.js"; +import DownloadIcon from "@mui/icons-material/Download"; +import FullscreenIcon from "@mui/icons-material/Fullscreen"; +import CloseIcon from "@mui/icons-material/Close"; +import { alpha } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import { downloadArtifact } from "../../utils/downloadArtifact"; + +// Fill 75% of the viewport, matching ArtifactViewer's fullscreen sizing. +const fullscreenHeight = + typeof window !== "undefined" + ? Math.max(360, Math.round(window.innerHeight * 0.75) - 64) + : 720; + +/** + * Download + fullscreen affordance for a plain (non artifact) Plotly chart - + * Live Metrics panels and session comparison graphs build their `data`/ + * `layout` client side from raw metrics, so they have no backend artifact to + * hand ArtifactViewer. This reuses the same PNG/SVG export plumbing + * (`downloadArtifact`) and the same lightbox look, without the edit chrome + * those charts don't need. + */ +export default function PlotActions({ + getContainer, + data, + layout, + filename, + sx, +}) { + const { t } = useTranslation(["explainers", "common"]); + const [anchorEl, setAnchorEl] = useState(null); + const [fullscreen, setFullscreen] = useState(false); + + const actionButtonSx = { + color: "text.secondary", + "&:hover": { color: "text.primary" }, + }; + + const handleDownload = (format) => { + setAnchorEl(null); + const container = getContainer(); + const plotEl = container?.querySelector(".js-plotly-plot"); + if (!plotEl) return; + downloadArtifact({ type: "plotly", title: filename }, { plotEl, format }); + }; + + return ( + <> + alpha(theme.palette.background.paper, 0.85), + backdropFilter: "blur(4px)", + border: (theme) => `1px solid ${theme.palette.ui.border}`, + ...sx, + }} + > + + setAnchorEl(e.currentTarget)} + sx={actionButtonSx} + > + + + + + setFullscreen(true)} + sx={actionButtonSx} + > + + + + + + setAnchorEl(null)} + > + handleDownload("png")}> + {t("explainers:button.downloadPng")} + + handleDownload("svg")}> + {t("explainers:button.downloadSvg")} + + + + setFullscreen(false)} + transitionDuration={0} + keepMounted + slotProps={{ + paper: { + elevation: 0, + sx: { + bgcolor: "rgba(0, 0, 0, 0.15)", + backgroundImage: "none", + backdropFilter: "blur(6px)", + willChange: "backdrop-filter", + boxShadow: "none", + }, + }, + }} + > + { + if (e.target === e.currentTarget) setFullscreen(false); + }} + sx={{ + position: "relative", + flex: 1, + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + setFullscreen(false)} + aria-label={t("common:close")} + sx={{ + position: "absolute", + top: 16, + right: 16, + zIndex: 1, + width: 36, + height: 36, + color: "#fff", + bgcolor: "rgba(255, 255, 255, 0.12)", + border: "1px solid rgba(255, 255, 255, 0.2)", + "&:hover": { bgcolor: "rgba(255, 255, 255, 0.25)" }, + }} + > + + + + + {fullscreen && ( + + )} + + + + + ); +} + +PlotActions.propTypes = { + getContainer: PropTypes.func.isRequired, + data: PropTypes.array.isRequired, + layout: PropTypes.object, + filename: PropTypes.string, + sx: PropTypes.object, +}; diff --git a/DashAI/front/src/components/shared/RunStatusDot.jsx b/DashAI/front/src/components/shared/RunStatusDot.jsx new file mode 100644 index 000000000..9f279a83a --- /dev/null +++ b/DashAI/front/src/components/shared/RunStatusDot.jsx @@ -0,0 +1,40 @@ +import Box from "@mui/material/Box"; +import { useTheme } from "@mui/material/styles"; +import PropTypes from "prop-types"; +import { getRunStatusColor } from "../../utils/runStatus"; + +export default function RunStatusDot({ status, size, sx }) { + const theme = useTheme(); + const statusColorKey = getRunStatusColor(status); + const statusMain = + statusColorKey === "default" + ? theme.palette.text.disabled + : theme.palette[statusColorKey].main; + + return ( + + ); +} + +RunStatusDot.propTypes = { + status: PropTypes.number.isRequired, + size: PropTypes.number, + sx: PropTypes.object, +}; + +RunStatusDot.defaultProps = { + size: 8, + sx: {}, +}; diff --git a/DashAI/front/src/components/shared/SingleSelectChipGroup.jsx b/DashAI/front/src/components/shared/SingleSelectChipGroup.jsx index a4160bab6..fae7f9c57 100644 --- a/DashAI/front/src/components/shared/SingleSelectChipGroup.jsx +++ b/DashAI/front/src/components/shared/SingleSelectChipGroup.jsx @@ -1,34 +1,58 @@ -import { Chip, Grid } from "@mui/material"; +import { ToggleButton, ToggleButtonGroup } from "@mui/material"; import React from "react"; import PropTypes from "prop-types"; /** - * This component is a single select chip group + * This component is a single select toggle group — the options are rendered + * as one connected control (not separate buttons) so it reads as a switch + * between mutually exclusive choices, e.g. "Int" vs "Null" for a nullable + * field's type. * @param {Array} options - The options to display * @param {function} onChange - The function to update the selected option * @param {string} selected - The selected option */ const SingleSelectChipGroup = ({ options, onChange, selected }) => { - const handleChipClick = (option) => { - onChange(option); + const handleChange = (event, value) => { + if (value !== null) onChange(value); }; return ( - + {options.map((option, index) => ( - - handleChipClick(option.key)} - color={selected === option.key ? "primary" : "default"} - /> - + + {option.label} + ))} - + ); }; diff --git a/DashAI/front/src/components/shared/TableArtifact.jsx b/DashAI/front/src/components/shared/TableArtifact.jsx new file mode 100644 index 000000000..152bd6d97 --- /dev/null +++ b/DashAI/front/src/components/shared/TableArtifact.jsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { Box, TablePagination } from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; + +import "./leanDatasetTable/leanDatasetTable.css"; + +const TABLE_ROWS_PER_PAGE = 5; + +/** + * Table artifact content: a sticky-header table over a fixed-height, + * independently scrolling body with client-side pagination pinned below it. + * Reuses LeanDatasetTable's markup/CSS classes so it reads as the same + * table style, without pulling in that component's dataset-specific + * features (filtering, sorting, column visibility). + */ +export default function TableArtifact({ + columns, + rows, + highlightedCells, + height, +}) { + const theme = useTheme(); + const [page, setPage] = useState(0); + + // Reset to the first page whenever the underlying data changes (e.g. + // navigating to a sibling artifact in the fullscreen lightbox). + useEffect(() => setPage(0), [rows]); + + const pageStart = page * TABLE_ROWS_PER_PAGE; + const pageRows = rows.slice(pageStart, pageStart + TABLE_ROWS_PER_PAGE); + + return ( + +
+ + + + {columns.map((column) => ( + + ))} + + + + {pageRows.map((row, i) => { + const rowIndex = pageStart + i; + return ( + + {row.map((value, columnIndex) => ( + + ))} + + ); + })} + +
+ {column} +
+ {value === null ? "-" : String(value)} +
+
+ setPage(p)} + /> +
+ ); +} + +TableArtifact.propTypes = { + columns: PropTypes.arrayOf(PropTypes.string).isRequired, + rows: PropTypes.array.isRequired, + highlightedCells: PropTypes.instanceOf(Set).isRequired, + height: PropTypes.number.isRequired, +}; diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanCell.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanCell.jsx index d80325954..29e26a75f 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanCell.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanCell.jsx @@ -38,19 +38,35 @@ function highlight(text, query) { return parts; } -const LeanCell = memo(function LeanCell({ value, query }) { +const LeanCell = memo(function LeanCell({ + value, + query, + isPinned, + pinnedOffset, +}) { + const className = isPinned ? "lean-cell lean-cell--pinned" : "lean-cell"; + const style = isPinned ? { right: pinnedOffset ?? 0 } : undefined; const isImage = typeof value === "string" && value.startsWith("data:image"); if (isImage) { - return
{renderValue(value)} + {renderValue(value)} + + {query ? highlight(text, query) : text}
`` with a * sticky header and server-side pagination via ``fetchPage``. Supports @@ -35,10 +40,21 @@ function LeanDatasetTable({ enableRowsPerPage = true, enableColumnVisibility = true, showExportButton = true, + onRowClick = null, + selectedRowIndex = null, + enableRowSelection = false, + selectedRowIndices = null, + onRowSelectionChange = null, + rowActions, + targetColumn, + editableRows = [], + infiniteScroll = false, + loadMoreStep = 25, + extraActions, }) { const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); - const { t } = useTranslation(["datasets"]); + const { t } = useTranslation(["datasets", "common"]); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(initialPageSize); const [rows, setRows] = useState([]); @@ -54,17 +70,17 @@ function LeanDatasetTable({ const [debouncedFilterOperators, setDebouncedFilterOperators] = useState({}); const filterDebounceRef = useRef(null); const [searchValue, setSearchValue] = useState(""); - // Debounced version drives the cell highlighting work. Re-rendering every + // Debounced version drives the cell highlighting work. Re rendering every // cell on each keystroke is the dominant cost on wide datasets. const [debouncedSearchValue, setDebouncedSearchValue] = useState(""); const searchDebounceRef = useRef(null); const [columnsAnchor, setColumnsAnchor] = useState(null); const [editingColumn, setEditingColumn] = useState(null); - // Single-column sort: ``{ id: columnName, desc: boolean }`` or null. + // Single column sort: ``{ id: columnName, desc: boolean }`` or null. const [sort, setSort] = useState(null); // Bumped after operations that mutate the dataset (e.g. column rename) to - // force the next fetch effect to re-run and pull rows with the new schema. + // force the next fetch effect to re run and pull rows with the new schema. const [refreshKey, setRefreshKey] = useState(0); useEffect(() => { @@ -170,13 +186,16 @@ function LeanDatasetTable({ const allColumnKeys = rows.length > 0 - ? Object.keys(rows[0]).filter((k) => k !== "id") + ? Object.keys(rows[0]).filter((k) => k !== "id" && !k.startsWith("__")) : Object.keys(columnTypes); - const visibleColumnKeys = useMemo( - () => allColumnKeys.filter((k) => !hiddenColumns.has(k)), - [allColumnKeys, hiddenColumns], - ); + const visibleColumnKeys = useMemo(() => { + const base = allColumnKeys.filter((k) => !hiddenColumns.has(k)); + if (targetColumn && base.includes(targetColumn)) { + return [...base.filter((k) => k !== targetColumn), targetColumn]; + } + return base; + }, [allColumnKeys, hiddenColumns, targetColumn]); const highlightQuery = debouncedSearchValue.trim(); @@ -218,7 +237,7 @@ function LeanDatasetTable({ ); // Stable callbacks for the memoized toolbar - without these, the toolbar - // re-renders on every page / row change because the inline arrows would + // re renders on every page / row change because the inline arrows would // change reference each render. const handleOpenColumnsMenu = useCallback( (e) => setColumnsAnchor(e.currentTarget), @@ -283,6 +302,71 @@ function LeanDatasetTable({ const handleCancelRename = useCallback(() => setEditingColumn(null), []); + // Absolute (cross page) index of each rendered row. This is the row's real + // position in the dataset only while sort/filter are off, so callers that + // rely on the selection for indexing should keep those disabled. + const pageGlobalIndices = useMemo( + () => rows.map((_, i) => page * pageSize + i), + [rows, page, pageSize], + ); + + const allPageSelected = + enableRowSelection && + pageGlobalIndices.length > 0 && + selectedRowIndices != null && + pageGlobalIndices.every((idx) => selectedRowIndices.has(idx)); + const somePageSelected = + enableRowSelection && + selectedRowIndices != null && + pageGlobalIndices.some((idx) => selectedRowIndices.has(idx)); + + const toggleRowSelected = useCallback( + (globalIndex) => { + if (!onRowSelectionChange) return; + const next = new Set(selectedRowIndices ?? []); + if (next.has(globalIndex)) next.delete(globalIndex); + else next.add(globalIndex); + onRowSelectionChange(next); + }, + [onRowSelectionChange, selectedRowIndices], + ); + + const toggleAllOnPage = useCallback(() => { + if (!onRowSelectionChange) return; + const next = new Set(selectedRowIndices ?? []); + if (allPageSelected) { + pageGlobalIndices.forEach((idx) => next.delete(idx)); + } else { + pageGlobalIndices.forEach((idx) => next.add(idx)); + } + onRowSelectionChange(next); + }, [ + onRowSelectionChange, + selectedRowIndices, + allPageSelected, + pageGlobalIndices, + ]); + + // Grows the table by observing a sentinel below the last row rather than + // listening for scroll on `.lean-scroll`: the table has no bounded height, + // so it is the surrounding page that scrolls, not this container. + const sentinelRef = useRef(null); + useEffect(() => { + if (!infiniteScroll) return undefined; + const el = sentinelRef.current; + if (!el) return undefined; + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !loading && rows.length < total) { + setPageSize((p) => p + loadMoreStep); + } + }, + { rootMargin: "200px" }, + ); + observer.observe(el); + return () => observer.disconnect(); + }, [infiniteScroll, loading, rows.length, total, loadMoreStep]); + return ( + {enableRowSelection && ( + + )} {visibleColumnKeys.map((key) => { const type = getColType(key); const colSpec = columnTypes[key]; @@ -347,6 +445,8 @@ function LeanDatasetTable({ sortDir={sortDir} allColumnKeys={allColumnKeys} datasetId={datasetId} + isPinned={key === targetColumn} + pinnedOffset={rowActions ? ACTIONS_COLUMN_WIDTH : 0} onStartEdit={() => setEditingColumn(key)} onCommitEdit={handleCommitRename} onCancelEdit={handleCancelRename} @@ -355,9 +455,19 @@ function LeanDatasetTable({ /> ); })} + {rowActions && ( + + )} {enableFilters && showFilters && ( + {enableRowSelection && ( + )} - {rows.map((row, i) => ( - - {visibleColumnKeys.map((key) => ( - - ))} + {editableRows.map((draft) => ( + + {enableRowSelection && ( + + ); + })} + {rowActions && ( + + )} ))} + {rows.map((row, i) => { + const globalIndex = page * pageSize + i; + const isChecked = + enableRowSelection && + selectedRowIndices != null && + selectedRowIndices.has(globalIndex); + const isSelected = selectedRowIndex === globalIndex || isChecked; + const clickable = Boolean(onRowClick) || enableRowSelection; + const handleRowClick = onRowClick + ? () => onRowClick(row, globalIndex) + : enableRowSelection + ? () => toggleRowSelected(globalIndex) + : undefined; + return ( + + {enableRowSelection && ( + + )} + {visibleColumnKeys.map((key) => ( + + ))} + {rowActions && ( + + )} + + ); + })}
+ + +
+
{t("common:remove")}
+
+
+ )} {visibleColumnKeys.map((key) => { const type = getColType(key); const operator = @@ -369,6 +479,8 @@ function LeanDatasetTable({ type={type} operator={operator} value={filterValues[key]} + isPinned={key === targetColumn} + pinnedOffset={rowActions ? ACTIONS_COLUMN_WIDTH : 0} onOperatorChange={(op) => handleFilterOperatorChange(key, op) } @@ -376,20 +488,100 @@ function LeanDatasetTable({ /> ); })} + {rowActions && }
+ )} + {visibleColumnKeys.map((key) => { + const isPinned = key === targetColumn; + return ( + + {draft.renderCell(key)} + + {draft.renderActions ? draft.renderActions() : null} +
e.stopPropagation()} + > + toggleRowSelected(globalIndex)} + /> + + {rowActions(row)} +
- {!loading && rows.length === 0 && ( + {!loading && rows.length === 0 && editableRows.length === 0 && ( )} + {infiniteScroll && loading && rows.length > 0 && ( + + {t("datasets:table.loadingMore")} + + )} + {infiniteScroll && rows.length < total && ( +
+ )}
- setPage(p)} - onRowsPerPageChange={(e) => { - setPageSize(parseInt(e.target.value, 10)); - setPage(0); - }} - rowsPerPageOptions={enableRowsPerPage ? [10, 25, 50] : [pageSize]} - labelRowsPerPage={enableRowsPerPage ? undefined : ""} - slotProps={ - enableRowsPerPage - ? undefined - : { select: { sx: { display: "none" } } } - } - /> + {infiniteScroll ? ( + + + {t("datasets:table.rowsLoaded", { count: rows.length, total })} + + + ) : ( + setPage(p)} + onRowsPerPageChange={(e) => { + setPageSize(parseInt(e.target.value, 10)); + setPage(0); + }} + rowsPerPageOptions={ + enableRowsPerPage + ? [...new Set([pageSize, 10, 25, 50])].sort((a, b) => a - b) + : [pageSize] + } + labelRowsPerPage={enableRowsPerPage ? undefined : ""} + slotProps={ + enableRowsPerPage + ? undefined + : { select: { sx: { display: "none" } } } + } + /> + )}
); } @@ -444,6 +670,23 @@ LeanDatasetTable.propTypes = { enableRowsPerPage: PropTypes.bool, enableColumnVisibility: PropTypes.bool, showExportButton: PropTypes.bool, + onRowClick: PropTypes.func, + selectedRowIndex: PropTypes.number, + enableRowSelection: PropTypes.bool, + selectedRowIndices: PropTypes.instanceOf(Set), + onRowSelectionChange: PropTypes.func, + rowActions: PropTypes.func, + targetColumn: PropTypes.string, + editableRows: PropTypes.arrayOf( + PropTypes.shape({ + key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + renderCell: PropTypes.func.isRequired, + renderActions: PropTypes.func, + }), + ), + infiniteScroll: PropTypes.bool, + loadMoreStep: PropTypes.number, + extraActions: PropTypes.node, }; export default LeanDatasetTable; diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanFilterCell.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanFilterCell.jsx index cb9edf015..2c0fee64e 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanFilterCell.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanFilterCell.jsx @@ -10,6 +10,8 @@ const LeanFilterCell = memo(function LeanFilterCell({ type, operator, value, + isPinned, + pinnedOffset, onOperatorChange, onValueChange, }) { @@ -28,7 +30,12 @@ const LeanFilterCell = memo(function LeanFilterCell({ const opLabel = (op) => t(`datasets:table.op.${op}`, { defaultValue: op }); return ( - +
- {type || "-"} + {type || null} {type === "Categorical" && datasetId && ( )} + {isPinned && ( + + {t("datasets:table.predictedColumn")} + + )}
@@ -89,6 +99,8 @@ LeanHeaderCell.propTypes = { sortDir: PropTypes.oneOf(["asc", "desc", null]), allColumnKeys: PropTypes.arrayOf(PropTypes.string).isRequired, datasetId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + isPinned: PropTypes.bool, + pinnedOffset: PropTypes.number, onStartEdit: PropTypes.func.isRequired, onCommitEdit: PropTypes.func.isRequired, onCancelEdit: PropTypes.func.isRequired, diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx index 9049f3be3..ceea4df23 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx @@ -32,57 +32,22 @@ const LeanToolbar = memo(function LeanToolbar({ onSearchChange, onClearSearch, onExport, + extraActions, }) { const { t } = useTranslation(["datasets"]); - return ( - - {/* Left: export */} - {showExportButton && ( - - - - - - )} - + if ( + !enableFilters && + !enableSearch && + !enableColumnVisibility && + !showExportButton + ) { + return null; + } - {/* Right: hidden-count badge, column-visibility, filters, search */} - {hiddenColumnsCount > 0 && ( - - {t("datasets:table.hiddenCount", { count: hiddenColumnsCount })} - - )} - {enableColumnVisibility && ( - - - - - - )} + return ( + + {/* Left: filters, search, hidden-count badge, column-visibility */} {enableFilters && ( )} + {hiddenColumnsCount > 0 && ( + + {t("datasets:table.hiddenCount", { count: hiddenColumnsCount })} + + )} + {enableColumnVisibility && ( + + + + + + )} + + + + {/* Right: export, then any per-table custom actions */} + {showExportButton && ( + + + + + + )} + {extraActions && ( + + {extraActions} + + )} ); }); @@ -141,6 +157,7 @@ LeanToolbar.propTypes = { onSearchChange: PropTypes.func.isRequired, onClearSearch: PropTypes.func.isRequired, onExport: PropTypes.func.isRequired, + extraActions: PropTypes.node, }; export default LeanToolbar; diff --git a/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css b/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css index 20bd751ad..66062253c 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css +++ b/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css @@ -4,6 +4,9 @@ flex-direction: column; min-height: 0; width: 100%; + /* Fill a height constrained parent (a no op when the parent height is + indefinite) so the scroll area grows and pagination pins to the bottom. */ + height: 100%; } .lean-loading { @@ -15,6 +18,9 @@ .lean-scroll { overflow: auto; + flex: 1 1 auto; + min-height: 0; + background: var(--lean-body-bg, transparent); border: 1px solid rgba(128, 128, 128, 0.3); border-bottom: none; border-radius: 4px 4px 0 0; @@ -47,6 +53,83 @@ white-space: nowrap; } +/* The tint is layered as an opaque gradient over the solid header/body + background (rather than a plain rgba background) so the pinned column + fully occludes whatever scrolls underneath it — a translucent background + here would let other columns show through while sticky. */ +.lean-table thead th.lean-th--pinned { + z-index: 2; + min-width: 160px; + background: linear-gradient( + rgba(100, 150, 255, 0.16), + rgba(100, 150, 255, 0.16) + ), + var(--lean-header-bg, #1f1f1f); + box-shadow: -2px 0 4px rgba(0, 0, 0, 0.35); +} + +.lean-th--pinned .lean-th-name { + overflow: hidden; + text-overflow: ellipsis; +} + +.lean-cell--pinned { + position: sticky; + z-index: 1; + background: linear-gradient( + rgba(100, 150, 255, 0.08), + rgba(100, 150, 255, 0.08) + ), + var(--lean-body-bg, #1f1f1f); + box-shadow: -2px 0 4px rgba(0, 0, 0, 0.35); +} + +.lean-th-filter--pinned { + z-index: 2; + background: linear-gradient( + rgba(100, 150, 255, 0.16), + rgba(100, 150, 255, 0.16) + ), + var(--lean-header-bg, #1f1f1f); + box-shadow: -2px 0 4px rgba(0, 0, 0, 0.35); +} + +.lean-th--actions, +.lean-cell--actions { + position: sticky; + right: 0; + width: 64px; + min-width: 64px; +} + +.lean-table thead th.lean-th--actions { + z-index: 2; + min-width: 64px; + background: var(--lean-header-bg, #1f1f1f); +} + +.lean-cell--actions { + z-index: 1; + background: var(--lean-body-bg, #1f1f1f); +} + +.lean-target-chip { + display: inline-flex; + align-items: center; + height: 16px; + padding: 0 6px; + font-size: 9px; + font-weight: 700; + font-family: inherit; + color: rgb(100, 150, 255); + background: rgba(100, 150, 255, 0.15); + border: 1px solid rgba(100, 150, 255, 0.4); + border-radius: 8px; + text-transform: uppercase; + letter-spacing: 0.3px; + white-space: nowrap; +} + .lean-th-name { font-weight: 600; font-size: 13px; @@ -60,7 +143,6 @@ opacity: 0.65; } - .lean-cell { padding: 4px 10px; border-bottom: 1px solid rgba(128, 128, 128, 0.15); @@ -70,6 +152,25 @@ max-width: 320px; } +.lean-row--editable .lean-cell { + background: rgba(100, 150, 255, 0.08); + overflow: visible; + white-space: normal; +} + +/* Pinned/actions cells are sticky and need an opaque background (see the + pinned-column rules above) — a translucent tint here would let scrolled + content bleed through, so composite the tint over the solid body color + instead of using a plain rgba background. */ +.lean-row--editable .lean-cell--pinned, +.lean-row--editable .lean-cell--actions { + background: linear-gradient( + rgba(100, 150, 255, 0.16), + rgba(100, 150, 255, 0.16) + ), + var(--lean-body-bg, #1f1f1f); +} + .lean-toolbar { display: flex; align-items: center; @@ -291,3 +392,36 @@ .lean-sort--desc .lean-sort-arrow--down { opacity: 1; } + +/* Overrides the base `.lean-table thead th.lean-th` rule's min-width: 120px + (that selector is more specific than `.lean-th--select` alone) - this + column only ever holds a checkbox, so it should never be forced that wide. */ +.lean-table thead th.lean-th--select { + /* width: 1% is a standard trick for auto-layout tables: it makes the + browser shrink this column to its content's width instead of sharing in + the extra space distributed across columns when the table stretches to + fill its container (min-width: 100% on .lean-table). */ + width: 1%; + min-width: 0; + white-space: nowrap; + /* Same top padding as every other header cell (padding: 6px 10px on + .lean-th) so the checkbox lines up with the other columns' name text + instead of sitting flush against the top of the (taller) header row. */ + padding: 6px 4px 0; +} + +.lean-td--select { + width: 1%; + white-space: nowrap; + padding: 0 4px; + text-align: center; + border-bottom: 1px solid rgba(128, 128, 128, 0.15); +} + +.lean-row--clickable { + cursor: pointer; +} + +.lean-row--clickable:hover { + background: var(--lean-row-hover, rgba(255, 255, 255, 0.06)); +} diff --git a/DashAI/front/src/components/threeSectionLayout/DeleteConfirmationModal.jsx b/DashAI/front/src/components/threeSectionLayout/DeleteConfirmationModal.jsx index 688f73345..93210664b 100644 --- a/DashAI/front/src/components/threeSectionLayout/DeleteConfirmationModal.jsx +++ b/DashAI/front/src/components/threeSectionLayout/DeleteConfirmationModal.jsx @@ -26,6 +26,10 @@ export default function DeleteConfirmationModal({ slotProps={{ transition: { onExited } }} > e.stopPropagation()} sx={{ position: "absolute", top: "50%", diff --git a/DashAI/front/src/components/threeSectionLayout/OptionBox.jsx b/DashAI/front/src/components/threeSectionLayout/OptionBox.jsx index b262d8d5a..9e3502ecb 100644 --- a/DashAI/front/src/components/threeSectionLayout/OptionBox.jsx +++ b/DashAI/front/src/components/threeSectionLayout/OptionBox.jsx @@ -1,39 +1,72 @@ -import { useRef, useState, useEffect } from "react"; -import { Box, Typography, ButtonBase, Tooltip, useTheme } from "@mui/material"; +import { forwardRef, useRef, useLayoutEffect } from "react"; +import { Box, Typography, ButtonBase, useTheme } from "@mui/material"; -const DESCRIPTION_MAX_LINES = 3; +// Must match the top+bottom padding set below ("22px 24px"). +const VERTICAL_PADDING = 44; +// Must match the footer's mt below. +const FOOTER_GAP = 16; -export default function OptionBox({ - optionName, - description, - onClick, - Icon = null, - chips = [], - dataTour, - ...otherProps -}) { - const descRef = useRef(null); - const [isTruncated, setIsTruncated] = useState(false); - - useEffect(() => { - const el = descRef.current; - if (el) { - setIsTruncated(el.scrollHeight > el.clientHeight); - } - }, [description]); +const OptionBox = forwardRef(function OptionBox( + { + optionName, + description, + onClick, + Icon = null, + chips = [], + dataTour, + minHeight, + onMeasure, + ...otherProps + }, + ref, +) { const theme = useTheme(); const accent = theme.palette.primary.main; const accentDim = `${theme.palette.primary.main}1F`; const accentBorder = `${theme.palette.primary.main}38`; const accentGlow = `${theme.palette.primary.main}0A`; + const contentRef = useRef(null); + const footerRef = useRef(null); + // Kept in a ref so the observer effect below doesn't need to reconnect + // every time the parent re-renders and hands us a new function identity. + const onMeasureRef = useRef(onMeasure); + onMeasureRef.current = onMeasure; + + // Reports this card's own natural (unconstrained) required height, so the + // parent can size every card to whichever one needs the most space. + // contentRef/footerRef never stretch (the spacer between them absorbs any + // extra space from an applied minHeight), so this reading is always the + // card's true minimum, regardless of what minHeight is currently applied. + // The observer itself is only (re)created when the card's own content + // changes, not on every parent re-render, to avoid amplifying updates + // during a live resize into a runaway render cascade. + useLayoutEffect(() => { + if (!contentRef.current || !footerRef.current) return; + const report = () => { + onMeasureRef.current?.( + contentRef.current.offsetHeight + + footerRef.current.offsetHeight + + FOOTER_GAP + + VERTICAL_PADDING, + ); + }; + report(); + const observer = new ResizeObserver(report); + observer.observe(contentRef.current); + observer.observe(footerRef.current); + return () => observer.disconnect(); + }, [optionName, description]); + return ( - {/* Header: icon */} - {Icon && ( - - - + + {/* Header: icon */} + {Icon && ( + + + + - - )} + )} - {/* Title */} - - {optionName} - + {/* Title */} + + {optionName} + - {/* Description */} - - - - {description} - - - + {/* Description */} + + {description} + + + + {/* Spacer: absorbs extra height so the footer stays pinned to the bottom */} + {/* Footer: chips + arrow */} ); -} +}); + +export default OptionBox; diff --git a/DashAI/front/src/components/threeSectionLayout/SelectOptionMenu.jsx b/DashAI/front/src/components/threeSectionLayout/SelectOptionMenu.jsx index 39b5dbfc4..fd2a80058 100644 --- a/DashAI/front/src/components/threeSectionLayout/SelectOptionMenu.jsx +++ b/DashAI/front/src/components/threeSectionLayout/SelectOptionMenu.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useMemo, useCallback } from "react"; import { Box, Grid, Button, Alert, AlertTitle, Skeleton } from "@mui/material"; function useContainerColumns(ref) { @@ -42,6 +42,27 @@ export default function SelectOptionMenu({ const gridRef = useRef(null); const colSize = useContainerColumns(gridRef); + // Cards share a single dynamic height, sized to fit whichever card's + // content needs the most space, so text never gets cut off. Each OptionBox + // measures its own natural (unconstrained) height and reports it here. + const [cardHeights, setCardHeights] = useState({}); + const visibleNames = useMemo( + () => filteredOptions.map((option) => option.name).join("|"), + [filteredOptions], + ); + useEffect(() => { + setCardHeights({}); + }, [visibleNames]); + const handleMeasure = useCallback((name, height) => { + setCardHeights((prev) => + prev[name] === height ? prev : { ...prev, [name]: height }, + ); + }, []); + const cardHeightValues = Object.values(cardHeights); + const cardHeight = cardHeightValues.length + ? Math.max(...cardHeightValues) + : null; + return ( goToNextStep(option.name)} Icon={Icon} + minHeight={cardHeight} + onMeasure={(height) => handleMeasure(option.name, height)} dataTour={ dataTour && dataTourTarget && name === dataTourTarget ? dataTour diff --git a/DashAI/front/src/components/threeSectionLayout/panelContainers/CenterBox.jsx b/DashAI/front/src/components/threeSectionLayout/panelContainers/CenterBox.jsx index dc0e6731e..9cfa07d36 100644 --- a/DashAI/front/src/components/threeSectionLayout/panelContainers/CenterBox.jsx +++ b/DashAI/front/src/components/threeSectionLayout/panelContainers/CenterBox.jsx @@ -11,8 +11,9 @@ export default function CenterBox({ children }) { sx={{ border: `0.1px solid ${theme.palette.divider}`, borderTop: "none", + overflow: "auto", + scrollbarGutter: "stable", }} - overflow={"auto"} p={2} > {children} diff --git a/DashAI/front/src/constants/tours/modelsSessionTour.js b/DashAI/front/src/constants/tours/modelsSessionTour.js index b6c3596f6..cb1ed7ca6 100644 --- a/DashAI/front/src/constants/tours/modelsSessionTour.js +++ b/DashAI/front/src/constants/tours/modelsSessionTour.js @@ -156,27 +156,6 @@ export const modelsSessionTourSteps = [ disableBeacon: true, maxWidth: "420px", }, - { - target: '[data-tour="graphs-button"]', - content: ( - -
-

-

- -

-

- -

-
-
- ), - placement: "bottom", - disableBeacon: true, - spotlightClicks: true, - isInteractive: true, - disableBackButton: true, - }, { target: '[data-tour="model-comparison-panel"]', content: ( diff --git a/DashAI/front/src/contexts/ThemeContext.jsx b/DashAI/front/src/contexts/ThemeContext.jsx index 01647a617..86a4eee17 100644 --- a/DashAI/front/src/contexts/ThemeContext.jsx +++ b/DashAI/front/src/contexts/ThemeContext.jsx @@ -29,6 +29,19 @@ export function CustomThemeProvider({ children }) { const theme = useMemo(() => createTheme(getTheme(mode)), [mode]); + // Plotly.js hardcodes its updatemenus (dropdown selector) hover/active item + // background to a near-white color with no layout option to override it - + // see plotly.js/src/components/updatemenus/constants.js. Stamping the mode + // here lets a plain CSS rule (index.css) neutralize it in dark mode only, + // where it makes the selected item's text unreadable. + useEffect(() => { + document.documentElement.dataset.theme = mode; + document.documentElement.style.setProperty( + "--dashai-plot-menu-highlight", + theme.palette.background.paper, + ); + }, [mode, theme]); + return ( {children} diff --git a/DashAI/front/src/contexts/schema.js b/DashAI/front/src/contexts/schema.js index ab9e4da5f..a42b22c77 100644 --- a/DashAI/front/src/contexts/schema.js +++ b/DashAI/front/src/contexts/schema.js @@ -154,14 +154,17 @@ export const useFormSchemaStore = () => { if (properties.length === 0) return getModelFromSubform(formValues[property]); + // Walk down the property chain, unwrapping each subform into its params + // map. This must unwrap the last property too: the current view's params + // map is what holds the field being rendered. Without unwrapping the last + // hop, a component field nested inside another component (depth >= 2) reads + // undefined and its select never reflects a selection. let params = null; for (const prop of properties) { - if (params === null) { - params = formValues[prop.key]; - continue; - } - - params = getParamsFromSubform(params)[prop.key]; + params = + params === null + ? getParamsFromSubform(formValues[prop.key]) + : getParamsFromSubform(params[prop.key]); } return getModelFromSubform(params[property]); }; diff --git a/DashAI/front/src/hooks/useFormSchema.js b/DashAI/front/src/hooks/useFormSchema.js index fbff58620..11e44cf52 100644 --- a/DashAI/front/src/hooks/useFormSchema.js +++ b/DashAI/front/src/hooks/useFormSchema.js @@ -41,16 +41,36 @@ function useFormSchema({ } }, [formSubmitRef, formik]); + // The shared formValues context starts empty on every fresh mount (e.g. + // switching wizard steps remounts this hook) and gets seeded from + // initialValues/defaultValues here, one render after mount. hasPendingSeed + // is exposed so the onValuesChange effect below can avoid notifying the + // parent with this still-empty value before the seed lands. + const formValuesEmpty = Object.keys(formValues ?? {}).length === 0; + const hasInitialValues = Boolean( + initialValues && Object.keys(initialValues).length > 0, + ); + const hasDefaultValues = Boolean( + defaultValues && Object.keys(defaultValues).length > 0, + ); + const hasPendingSeed = + formValuesEmpty && (hasInitialValues || hasDefaultValues); + // Updates the formik schema with the merged initial values if the formValues is empty useEffect(() => { - if (formValues && Object.keys(formValues).length === 0) { - if (initialValues && Object.keys(initialValues).length > 0) { - handleUpdateSchema({ ...defaultValues, ...initialValues }); - } else if (defaultValues && Object.keys(defaultValues).length > 0) { - handleUpdateSchema(defaultValues); - } + if (!formValuesEmpty) return; + if (hasInitialValues) { + handleUpdateSchema({ ...defaultValues, ...initialValues }); + } else if (hasDefaultValues) { + handleUpdateSchema(defaultValues); } - }, [formValues, initialValues, defaultValues]); + }, [ + formValuesEmpty, + hasInitialValues, + hasDefaultValues, + initialValues, + defaultValues, + ]); // Sets the error state of the form if setError is not null useEffect(() => { @@ -60,11 +80,16 @@ function useFormSchema({ } }, [formik.errors, setError]); + // Skip while a seed is pending — the shared context still holds the empty + // value from this fresh mount, and notifying now would overwrite the + // parent's already-correct saved values with that empty object for a beat + // (visible as a flash of schema-default values) before the seed effect + // above corrects it. useEffect(() => { - if (onValuesChange) { + if (onValuesChange && !hasPendingSeed) { onValuesChange(); } - }, [formik.values]); + }, [formik.values, hasPendingSeed]); const formProps = { formik, diff --git a/DashAI/front/src/hooks/useMetricByTask.js b/DashAI/front/src/hooks/useMetricByTask.js index 8516dd2bc..22b7d6527 100644 --- a/DashAI/front/src/hooks/useMetricByTask.js +++ b/DashAI/front/src/hooks/useMetricByTask.js @@ -4,7 +4,10 @@ import { useSnackbar } from "notistack"; export default function useOptimizersByTask({ taskName }) { const [compatibleMetrics, setCompatibleMetrics] = useState([]); - const [loading, setLoading] = useState(false); + // Starts true: the mount effect below always fetches, so there's no render + // where "not loading yet" is a real state — starting false let consumers + // briefly treat an empty, not-yet-fetched list as final for one frame. + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { enqueueSnackbar } = useSnackbar(); diff --git a/DashAI/front/src/hooks/useModelParents.js b/DashAI/front/src/hooks/useModelParents.js index 2a6ab93b1..d187552e4 100644 --- a/DashAI/front/src/hooks/useModelParents.js +++ b/DashAI/front/src/hooks/useModelParents.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { getComponents } from "../api/component"; /* @@ -30,5 +30,21 @@ export default function useModelParents({ parent }) { } }, [parent]); - return { models, loading }; + // Flip a single model's downloaded flag in place so an inline download/delete + // is reflected in the cached list. Without this, switching models and coming + // back would re-mount the download control from the stale (not-downloaded) + // flag and show the Download button again. + const markDownloaded = useCallback((name, isDownloaded) => { + setModels((prev) => + prev + ? prev.map((model) => + model.name === name + ? { ...model, downloaded: isDownloaded } + : model, + ) + : prev, + ); + }, []); + + return { models, loading, markDownloaded }; } diff --git a/DashAI/front/src/hooks/useRunEditForm.js b/DashAI/front/src/hooks/useRunEditForm.js new file mode 100644 index 000000000..b11622e76 --- /dev/null +++ b/DashAI/front/src/hooks/useRunEditForm.js @@ -0,0 +1,234 @@ +import { useState, useEffect, useMemo, useCallback } from "react"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import useSchema from "./useSchema"; +import { updateRunParameters, getRunOperationsCount } from "../api/run"; +import { checkIfHaveOptimazers } from "../utils/schema"; + +/** + * Shared editable-parameters state/logic for a run's "Edit Run" dialog + * (RunEditDialog), used from the compact model card, RunCard, and the model + * detail view, so every entry point behaves identically. + * + * `enabled` gates the reset-on-mount and operations-count fetch: pass the + * dialog's `open` flag so the form resets every time it reopens. + */ +export default function useRunEditForm({ + run, + session, + existingRuns = [], + onRefresh, + onSaved, + enabled = true, +}) { + const { t } = useTranslation(["models", "common"]); + const { enqueueSnackbar } = useSnackbar(); + + const [editedName, setEditedName] = useState(run.name || ""); + const [editedParameters, setEditedParameters] = useState( + run.parameters || {}, + ); + const [editedOptimizer, setEditedOptimizer] = useState( + run.optimizer_name || "", + ); + const [editedOptimizerParams, setEditedOptimizerParams] = useState( + run.optimizer_parameters || {}, + ); + const [editedGoalMetric, setEditedGoalMetric] = useState( + run.goal_metric || "", + ); + const [operationsCount, setOperationsCount] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [saveConfirmOpen, setSaveConfirmOpen] = useState(false); + + const { defaultValues: defaultOptimizerParams } = useSchema({ + modelName: enabled ? editedOptimizer : null, + }); + + const runId = run.id; + + useEffect(() => { + if (!enabled) return; + setEditedName(run.name || ""); + setEditedParameters(run.parameters || {}); + setEditedOptimizer(run.optimizer_name || ""); + setEditedOptimizerParams(run.optimizer_parameters || {}); + setEditedGoalMetric(run.goal_metric || ""); + }, [runId, enabled]); + + useEffect(() => { + if (!enabled || !runId) return; + getRunOperationsCount(runId.toString()) + .then(setOperationsCount) + .catch((error) => + console.error("Error fetching operations count:", error), + ); + }, [enabled, runId]); + + const hasOptimizableParams = useMemo( + () => checkIfHaveOptimazers(editedParameters), + [editedParameters], + ); + + const isDirty = useMemo(() => { + if (editedName.trim() !== (run.name || "")) return true; + if ( + JSON.stringify(editedParameters) !== JSON.stringify(run.parameters || {}) + ) + return true; + if (editedOptimizer !== (run.optimizer_name || "")) return true; + if ( + JSON.stringify(editedOptimizerParams) !== + JSON.stringify(run.optimizer_parameters || {}) + ) + return true; + if (editedGoalMetric !== (run.goal_metric || "")) return true; + return false; + }, [ + editedName, + editedParameters, + editedOptimizer, + editedOptimizerParams, + editedGoalMetric, + run, + ]); + + // While a parameter is marked for optimization, an optimizer and a goal + // metric are both required — keep Save disabled until they're set instead + // of letting the user click it and only then learn what's missing. + const canSave = + isDirty && + (!hasOptimizableParams || (!!editedOptimizer && !!editedGoalMetric)); + + const doSave = async () => { + setSaveConfirmOpen(false); + setIsSaving(true); + try { + await updateRunParameters( + run.id.toString(), + editedName.trim(), + editedParameters, + editedOptimizer || "", + { ...defaultOptimizerParams, ...editedOptimizerParams }, + editedGoalMetric || "", + ); + + enqueueSnackbar( + t("models:message.runUpdatedSuccess", { runName: editedName }), + { variant: "success" }, + ); + + if (onRefresh) await onRefresh(); + if (onSaved) onSaved(); + } catch (error) { + console.error("Error updating run:", error); + enqueueSnackbar( + t("models:error.failedToUpdateRun", { + error: error.message || t("common:unknownError"), + }), + { variant: "error" }, + ); + } finally { + setIsSaving(false); + } + }; + + // Shared by the "Next" step transition (only the name needs to be valid to + // move on to the optimizer step) and the final save (which additionally + // requires the optimizer/goal metric once there are optimizable params). + const validateBasics = () => { + if (!editedName.trim()) { + enqueueSnackbar(t("models:error.runNameEmpty"), { variant: "warning" }); + return false; + } + + const nameExists = existingRuns.some( + (r) => + r.id !== run.id && + r.name && + r.name.toLowerCase() === editedName.trim().toLowerCase(), + ); + if (nameExists) { + enqueueSnackbar( + t("models:error.runNameExists", { name: editedName.trim() }), + { variant: "error" }, + ); + return false; + } + + return true; + }; + + const handleSaveEdit = async () => { + if (!validateBasics()) return; + + if (hasOptimizableParams) { + if (!editedOptimizer) { + enqueueSnackbar(t("models:error.selectOptimizerRequired"), { + variant: "warning", + }); + return; + } + if (!editedGoalMetric) { + enqueueSnackbar(t("models:error.selectGoalMetricRequired"), { + variant: "warning", + }); + return; + } + } + + // Re-fetch the count right before confirming — the sidebar stays mounted + // alongside the Predictions/Explainability tabs, so a value fetched once + // on mount can go stale if the user creates one there before saving. + try { + const currentOperationsCount = await getRunOperationsCount( + run.id.toString(), + ); + setOperationsCount(currentOperationsCount); + } catch (error) { + console.error("Error fetching operations count:", error); + } + + // Saving always resets the run to "Not Started" and clears its metrics, + // even with no existing operations to lose — always confirm first. + setSaveConfirmOpen(true); + }; + + const handleParametersChange = useCallback((values) => { + setEditedParameters(values); + }, []); + + const handleOptimizerParamsChange = useCallback((values) => { + setEditedOptimizerParams(values); + }, []); + + const handleOptimizerSelected = (optimizerName) => { + setEditedOptimizer(optimizerName); + setEditedOptimizerParams({}); + }; + + return { + editedName, + setEditedName, + editedParameters, + handleParametersChange, + editedOptimizer, + editedOptimizerParams, + setEditedOptimizerParams, + handleOptimizerParamsChange, + handleOptimizerSelected, + editedGoalMetric, + setEditedGoalMetric, + hasOptimizableParams, + isDirty, + canSave, + validateBasics, + operationsCount, + isSaving, + saveConfirmOpen, + setSaveConfirmOpen, + doSave, + handleSaveEdit, + taskName: session?.task_name, + }; +} diff --git a/DashAI/front/src/hooks/useSchema.js b/DashAI/front/src/hooks/useSchema.js index f01d6c43f..cc8545bbb 100644 --- a/DashAI/front/src/hooks/useSchema.js +++ b/DashAI/front/src/hooks/useSchema.js @@ -8,13 +8,48 @@ import { useTranslation } from "react-i18next"; * @param {string} modelName - The name of the model to get the schema */ +// This hook backs every configurable-object form in the app (pipelines, +// converters, explorers, dataloaders, models...), not just the dialogs that +// prompted this cache — a wizard step's own prefetch and the form it renders +// a step later both call this hook for the same model within milliseconds of +// each other, and without sharing that one fetch, the second consumer showed +// an empty form for a beat. But nothing here should keep serving a schema +// fetched minutes ago to some unrelated form (or care about a plugin +// install/uninstall changing it) — so the cache is short-lived: long enough +// to dedupe that one burst of near-simultaneous mounts, short enough that +// everything else still gets a fresh fetch per mount, same as before. +const CACHE_TTL_MS = 10_000; +const schemaCache = new Map(); // modelName -> { value, expiresAt } + +function getCachedSchema(modelName) { + if (!modelName) return null; + const entry = schemaCache.get(modelName); + if (!entry) return null; + if (Date.now() > entry.expiresAt) { + schemaCache.delete(modelName); + return null; + } + return entry.value; +} + +function setCachedSchema(modelName, value) { + schemaCache.set(modelName, { value, expiresAt: Date.now() + CACHE_TTL_MS }); +} + export default function useSchema({ modelName = null } = {}) { - const [model, setModel] = useState(null); + const [model, setModel] = useState(() => getCachedSchema(modelName)); const [loading, setLoading] = useState(false); const { t } = useTranslation(); useEffect(() => { let cancelled = false; + + const cached = getCachedSchema(modelName); + if (cached) { + setModel(cached); + return undefined; + } + setModel(null); const getModel = async () => { @@ -23,6 +58,7 @@ export default function useSchema({ modelName = null } = {}) { const result = await getComponents({ model: modelName }); const formattedSchema = await formattedModel(result?.schema); if (!cancelled) { + setCachedSchema(modelName, formattedSchema); setModel(formattedSchema); } } catch (error) { diff --git a/DashAI/front/src/index.css b/DashAI/front/src/index.css index 3f530ca8a..a613839af 100644 --- a/DashAI/front/src/index.css +++ b/DashAI/front/src/index.css @@ -1,8 +1,8 @@ body { margin: 0; - font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", - "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", - "Helvetica Neue", sans-serif; + font-family: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -11,3 +11,12 @@ code { font-family: "Geist Mono", source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +/* Plotly.js hardcodes the hover/active background of its updatemenus + (dropdown selector) items to a near-white color with no way to theme it + via layout options (plotly.js/src/components/updatemenus/constants.js). + In dark mode this makes the selected item's (themed, light-colored) text + unreadable, so force it back to the paper background here instead. */ +[data-theme="dark"] .updatemenu-item-rect { + fill: var(--dashai-plot-menu-highlight, #1f1e1d) !important; +} diff --git a/DashAI/front/src/pages/generative/GenerativeContent.jsx b/DashAI/front/src/pages/generative/GenerativeContent.jsx index 29587ec43..7aee51179 100644 --- a/DashAI/front/src/pages/generative/GenerativeContent.jsx +++ b/DashAI/front/src/pages/generative/GenerativeContent.jsx @@ -97,23 +97,19 @@ export default function GenerativeContent() { return ; }; - const layout = ( - - - - - - {renderCenter()} - - {renderRight()} - - - - ); - - return isCreating ? ( - {layout} - ) : ( - layout + return ( + + + + + + + {renderCenter()} + + {renderRight()} + + + + ); } diff --git a/DashAI/front/src/pages/models/ModelsContent.jsx b/DashAI/front/src/pages/models/ModelsContent.jsx index e5f82e5e3..57c3df4d4 100644 --- a/DashAI/front/src/pages/models/ModelsContent.jsx +++ b/DashAI/front/src/pages/models/ModelsContent.jsx @@ -33,6 +33,7 @@ export default function ModelsContent() { selectDataset, setRuns, fetchRuns, + setActiveRunId, } = useModels(); useEffect(() => { @@ -43,6 +44,7 @@ export default function ModelsContent() { selectDataset(id); setSelectedSessionId(null); setSelectedTask(null); + setActiveRunId(null); setStep(2); return; } @@ -56,6 +58,7 @@ export default function ModelsContent() { if (task) { setSelectedTask(task); setSelectedSessionId(null); + setActiveRunId(null); setStep(1); } return; @@ -64,6 +67,7 @@ export default function ModelsContent() { if (path.startsWith("/app/models/sessions/") && params.id) { const id = Number(params.id); setSelectedSessionId(id); + setActiveRunId(params.runId ? Number(params.runId) : null); selectDataset(null); return; } @@ -71,6 +75,7 @@ export default function ModelsContent() { if (path === "/app/models" || path === "/app/models/") { setSelectedSessionId(null); setSelectedTask(null); + setActiveRunId(null); const preserved = location.state?.preselectedDatasetId; if (preserved != null) { selectDataset(preserved); @@ -84,6 +89,7 @@ export default function ModelsContent() { location.state?.preselectedDatasetId, params.id, params.taskName, + params.runId, tasks, ]); diff --git a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx index 70a7adfb1..a65ccacf7 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx @@ -1,12 +1,12 @@ import PropTypes from "prop-types"; import React, { useEffect, useMemo, useState } from "react"; -import { Alert, AlertTitle } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { useSnackbar } from "notistack"; import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; import { getComponents } from "../../../api/component"; -import graphsMaking, { heatmapMaking } from "../constants/graphsMaking"; +import { heatmapMaking, smallMultiplesMaking } from "../constants/graphsMaking"; import layoutMaking from "../constants/layoutMaking"; import ResultsGraphsLayout from "./ResultsGraphsLayout"; @@ -19,13 +19,15 @@ function ResultsGraphs({ const theme = useTheme(); const { t } = useTranslation(["models"]); - const [selectedChart, setSelectedChart] = useState("bar"); // Internal split state — used only when no controlled prop is provided - const [internalSplit, setInternalSplit] = useState("test"); + const [internalSplit, setInternalSplit] = useState("train"); const [selectedMetrics, setSelectedMetrics] = useState([]); const [chartData, setChartData] = useState({}); // { MetricName: { maximize: bool } } — fetched once on mount const [metricsMetadata, setMetricsMetadata] = useState({}); + // Run ids the user deselected from the legend — excluded from the charts + // but still listed (dimmed) so they can be toggled back on. + const [hiddenRunIds, setHiddenRunIds] = useState(() => new Set()); // Controlled or uncontrolled split const selectedSplit = splitProp ?? internalSplit; @@ -76,13 +78,15 @@ function ResultsGraphs({ }; }, [finishedRuns]); - // Auto-select a split only when running in uncontrolled mode + // Auto-select a split only when running in uncontrolled mode. Train is + // always the default landing split when entering a session; only fall + // back to another split if train genuinely has no metrics to show. useEffect(() => { if (splitProp !== undefined) return; - if (availableMetrics.test.length > 0) setInternalSplit("test"); + if (availableMetrics.train.length > 0) setInternalSplit("train"); else if (availableMetrics.validation.length > 0) setInternalSplit("validation"); - else if (availableMetrics.train.length > 0) setInternalSplit("train"); + else if (availableMetrics.test.length > 0) setInternalSplit("test"); }, [availableMetrics, splitProp]); useEffect(() => { @@ -97,34 +101,31 @@ function ResultsGraphs({ try { const metricsKey = `${selectedSplit}_metrics`; - const graphsToView = {}; - - finishedRuns.forEach((run, idx) => { - const metricsObj = run[metricsKey] ?? {}; - const values = selectedMetrics.map((m) => { - const v = metricsObj[m]; - if (v === undefined || v === null) return null; - if (Array.isArray(v)) return v[v.length - 1]?.value ?? null; - return typeof v === "number" ? v : null; - }); - graphsMaking(graphsToView, run, selectedMetrics, values, idx, theme); - }); - // Heatmap is a single all-runs trace — built after the loop. - graphsToView.heatmap = heatmapMaking( + // Bar view: one small chart per metric (small multiples) instead of + // one combined chart, so metrics with different scales/ranges never + // share an axis. Every run keeps the same color across all panels. + const { panels, legend, yaxis } = smallMultiplesMaking( finishedRuns, + hiddenRunIds, selectedMetrics, metricsKey, theme, metricsMetadata, ); - const { generalLayout } = layoutMaking( - selectedChart, - graphsToView, + // Heatmap is a single all-runs trace, unchanged. + const heatmap = heatmapMaking( + finishedRuns, + hiddenRunIds, + selectedMetrics, + metricsKey, theme, + metricsMetadata, ); - setChartData({ generalLayout, ...graphsToView }); + + const { generalLayout } = layoutMaking("heatmap", {}, theme); + setChartData({ generalLayout, bar: panels, legend, yaxis, heatmap }); } catch (error) { enqueueSnackbar(t("models:error.errorProcesingExperimentResults"), { variant: "error", @@ -133,16 +134,25 @@ function ResultsGraphs({ } }, [ finishedRuns, + hiddenRunIds, selectedSplit, selectedMetrics, - selectedChart, theme, metricsMetadata, enqueueSnackbar, t, ]); - const handleChangeChart = (chartType) => setSelectedChart(chartType); + // Reset deselected runs whenever the underlying run set changes (e.g. a + // run is deleted or a new one finishes), so a stale id can't stay hidden. + useEffect(() => { + const validIds = new Set(finishedRuns.map((r) => r.id)); + setHiddenRunIds((prev) => { + const next = new Set([...prev].filter((id) => validIds.has(id))); + return next.size === prev.size ? prev : next; + }); + }, [finishedRuns]); + const handleToggleMetric = (metric) => { const canonicalOrder = availableMetrics[selectedSplit] ?? []; setSelectedMetrics((prev) => { @@ -157,12 +167,25 @@ function ResultsGraphs({ setSelectedMetrics(availableMetrics[selectedSplit] ?? []); const handleClearAll = () => setSelectedMetrics([]); + const handleToggleRun = (runId) => { + setHiddenRunIds((prev) => { + const next = new Set(prev); + if (next.has(runId)) { + next.delete(runId); + } else { + next.add(runId); + } + return next; + }); + }; + if (finishedRuns.length === 0) { return ( - - No information from the experiments - There are no completed experiments or all have an error status. - + + + {t("models:label.noCompletedRuns")} + + ); } @@ -170,14 +193,14 @@ function ResultsGraphs({ return ( ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx index 426a8e381..178b436d5 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx @@ -2,19 +2,18 @@ import React from "react"; import PropTypes from "prop-types"; import { Box } from "@mui/material"; -import ResultsGraphsSelection from "./ResultsGraphsSelection"; import ResultsGraphsParameters from "./ResultsGraphsParameters"; import ResultsGraphsPlot from "./ResultsGraphsPlot"; function ResultsGraphsLayout({ - selectedChart, - handleChangeChart, currentMetrics, selectedMetrics, handleToggleMetric, handleSelectAll, handleClearAll, chartData, + onToggleRun, + sessionId, }) { return ( - {/* Chart type selector */} - - - - {/* Metric filter sidebar */} + {/* Metric filter toolbar */} + + - {/* Plotly chart area */} + {/* Plotly chart area — bar panels + heatmap in one grid */} + + {/* Remounts (resetting the drag-order state cleanly) whenever the + session changes instead of reusing the instance across sessions */} @@ -51,14 +50,14 @@ function ResultsGraphsLayout({ } ResultsGraphsLayout.propTypes = { - selectedChart: PropTypes.string.isRequired, - handleChangeChart: PropTypes.func.isRequired, currentMetrics: PropTypes.array.isRequired, selectedMetrics: PropTypes.array.isRequired, handleToggleMetric: PropTypes.func.isRequired, handleSelectAll: PropTypes.func.isRequired, handleClearAll: PropTypes.func.isRequired, chartData: PropTypes.object.isRequired, + onToggleRun: PropTypes.func.isRequired, + sessionId: PropTypes.number, }; export default ResultsGraphsLayout; diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx index e7f59622c..39b0e946d 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx @@ -1,13 +1,16 @@ -import React from "react"; +import React, { useMemo, useState } from "react"; import PropTypes from "prop-types"; import { Box, Button, Checkbox, FormControlLabel, + InputAdornment, + Popover, + TextField, Typography, } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; +import { BarChart, ExpandLess, ExpandMore, Search } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; function ResultsGraphsParameters({ @@ -17,91 +20,158 @@ function ResultsGraphsParameters({ handleSelectAll, handleClearAll, }) { - const theme = useTheme(); const { t } = useTranslation(["models", "common"]); + const [anchorEl, setAnchorEl] = useState(null); + const [search, setSearch] = useState(""); + const open = Boolean(anchorEl); + + const filteredMetrics = useMemo( + () => + currentMetrics.filter((metric) => + metric.toLowerCase().includes(search.toLowerCase()), + ), + [currentMetrics, search], + ); + + const handleClose = () => { + setAnchorEl(null); + setSearch(""); + }; return ( - - {/* ── Metric checkboxes ── */} - + <> + + + + + setSearch(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> - - - + {t("common:clear")} + - - {currentMetrics.length === 0 ? ( - - {t("models:label.noMetricsAvailableForThisView")} - - ) : ( - currentMetrics.map((metric) => ( - handleToggleMetric(metric)} - /> - } - label={{metric}} - sx={{ display: "flex", m: 0, py: 1 }} - /> - )) - )} - - + + ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index cc5740eee..362ed6809 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -1,65 +1,427 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import PropTypes from "prop-types"; import { Box, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; +import { DragIndicator } from "@mui/icons-material"; +import PlotActions from "../../../components/shared/PlotActions"; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + arrayMove, + rectSortingStrategy, + useSortable, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; -function ResultsGraphsPlot({ selectedChart, chartData }) { - const { t } = useTranslation(["models"]); +const PANEL_ORDER_STORAGE_KEY = "dashai-results-panel-order"; +const HEATMAP_ID = "__heatmap__"; +// Matches the run cards grid's minmax floor (SessionVisualization.jsx) so +// both sections switch between 1 and 2 columns at the same container width +// instead of disagreeing in a narrow "dead zone". +const PANEL_MIN_WIDTH = 340; +const GRID_GAP = 24; // gap: 3 → 3 * 8px + +function EmptyState({ message }) { + return ( + + {message} + + ); +} - const traceData = - selectedChart === "heatmap" - ? (chartData.heatmap ?? []) - : (chartData.bar ?? []); +/** + * One draggable card (a metric panel or the heatmap) — the drag handle is a + * small grip icon next to the title, so dragging never conflicts with + * hovering/clicking the plot itself (Plotly needs mouse events for its own + * hover tooltips). + */ +function SortableCard({ + id, + title, + gridColumn, + plotData, + plotLayout, + children, +}) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }); + const containerRef = useRef(null); - const hasData = traceData.length > 0; + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.4 : 1, + gridColumn, + }; - if (!hasData) { - return ( + return ( + { + setNodeRef(node); + containerRef.current = node; + }} + style={style} + sx={{ + border: 1, + borderColor: "divider", + borderRadius: 1, + p: 2, + "& .plot-actions": { opacity: 0, transition: "opacity 0.15s ease" }, + "&:hover .plot-actions, &:focus-within .plot-actions": { + opacity: 1, + }, + "@media (hover: none)": { + "& .plot-actions": { opacity: 1 }, + }, + }} + > - - {t("models:label.noMetricsAvailableForThisView")} - + + + + + + {title} + + + containerRef.current} + data={plotData} + layout={plotLayout} + filename={title} + /> + {children} + + ); +} + +function ResultsGraphsPlot({ chartData, onToggleRun, sessionId }) { + const { t } = useTranslation(["models"]); + const theme = useTheme(); + const bgColor = theme.palette.background.paper; + const textColor = theme.palette.text.primary; + const gridColor = theme.palette.divider; + + const panels = chartData.bar ?? []; + const legend = chartData.legend ?? []; + const yaxis = chartData.yaxis; + const heatmapData = chartData.heatmap ?? []; + + // Scoped per session — otherwise dragging a card in one session's Graphs + // would silently reorder every other session's Graphs too, since it's the + // same metric/heatmap ids everywhere. The parent remounts this component + // (via `key={sessionId}`) whenever the session changes, so this only needs + // to be read once per mount. + const storageKey = sessionId + ? `${PANEL_ORDER_STORAGE_KEY}-${sessionId}` + : PANEL_ORDER_STORAGE_KEY; + + const [order, setOrder] = useState(() => { + try { + const saved = localStorage.getItem(storageKey); + return saved ? JSON.parse(saved) : []; + } catch { + return []; + } + }); + + // Every draggable card — one per metric panel, plus the heatmap. + const cardIds = panels + .map((p) => p.metric) + .concat(heatmapData.length > 0 ? [HEATMAP_ID] : []); + + // Keep the stored order in sync with whatever cards are actually being + // shown right now — known cards keep their saved (customized) relative + // order; newly-seen ones are inserted at the natural position they'd have + // had if nothing were customized (i.e. right before whichever known card + // naturally comes after them), not always shoved to the very end. Without + // this, a heatmap dragged ahead of a single metric would permanently trap + // every later-appearing metric behind it, since "appended at the end" + // means "after the heatmap" too — which is exactly what made the heatmap's + // position look inconsistent across sessions. + const cardIdsKey = cardIds.join("|"); + useEffect(() => { + // Skip while chart data is still loading (cards momentarily empty) — + // reconciling against an empty list would wipe the saved order before + // the real cards ever arrive. + if (cardIds.length === 0) return; + setOrder((prev) => { + const known = prev.filter((id) => cardIds.includes(id)); + const missing = cardIds.filter((id) => !known.includes(id)); + const naturalIndex = new Map(cardIds.map((id, i) => [id, i])); + const next = [...known]; + missing.forEach((id) => { + const idx = naturalIndex.get(id); + const insertAt = next.findIndex((k) => naturalIndex.get(k) > idx); + next.splice(insertAt === -1 ? next.length : insertAt, 0, id); + }); + const unchanged = + next.length === prev.length && next.every((id, i) => id === prev[i]); + return unchanged ? prev : next; + }); + }, [cardIdsKey]); + + useEffect(() => { + if (order.length > 0) { + localStorage.setItem(storageKey, JSON.stringify(order)); + } + }, [order, storageKey]); + + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), + ); + + const handleDragEnd = (event) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + setOrder((prev) => { + const oldIndex = prev.indexOf(active.id); + const newIndex = prev.indexOf(over.id); + if (oldIndex === -1 || newIndex === -1) return prev; + return arrayMove(prev, oldIndex, newIndex); + }); + }; + + // The heatmap spans 2 grid columns, which forces the auto-fill grid to + // reserve 2 tracks even when the container is too narrow for two real + // 420px columns — CSS then resolves that shortage by splitting the + // columns unevenly instead of collapsing to one column per row. Only ask + // for the 2-column span once the container actually has room for it. + // + // Uses a state-backed (not plain useRef) callback ref: the grid only + // mounts once chartData finishes loading (before that, EmptyState renders + // instead), so a plain ref + `useEffect(..., [])` would fire before the + // node exists and never re-attach once it does. + const [gridNode, setGridNode] = useState(null); + const [canSpanTwoColumns, setCanSpanTwoColumns] = useState(true); + + useEffect(() => { + if (!gridNode) return undefined; + const TWO_COLUMN_MIN_WIDTH = PANEL_MIN_WIDTH * 2 + GRID_GAP; + const observer = new ResizeObserver(([entry]) => { + setCanSpanTwoColumns(entry.contentRect.width >= TWO_COLUMN_MIN_WIDTH); + }); + observer.observe(gridNode); + return () => observer.disconnect(); + }, [gridNode]); + + if (panels.length === 0 && heatmapData.length === 0) { + return ( + ); } + const orderedIds = order.filter((id) => cardIds.includes(id)); + + const panelLayout = { + autosize: true, + height: 240, + margin: { l: 110, r: 12, t: 8, b: 32 }, + showlegend: false, + paper_bgcolor: bgColor, + plot_bgcolor: bgColor, + bargap: 0.25, + font: { + color: textColor, + family: theme.typography.fontFamily, + size: 11, + }, + xaxis: { + gridcolor: gridColor, + zerolinecolor: gridColor, + tickfont: { color: textColor, size: 10 }, + }, + yaxis: { + gridcolor: gridColor, + tickfont: { color: textColor, size: 10 }, + automargin: true, + tickvals: yaxis?.tickvals, + ticktext: yaxis?.ticktext, + }, + }; + return ( - - + + {/* Shared legend — one entry per run, same color in every panel */} + {legend.length > 1 && ( + + {legend.map(({ id, label, color, hidden }) => ( + onToggleRun(id)} + sx={{ + display: "flex", + alignItems: "center", + gap: 1.5, + cursor: "pointer", + opacity: hidden ? 0.4 : 1, + userSelect: "none", + "&:hover": { opacity: hidden ? 0.65 : 0.8 }, + }} + > + + ))} + + )} + + + + + {orderedIds.map((id) => { + if (id === HEATMAP_ID) { + const heatmapLayout = { + ...(chartData.generalLayout ?? {}), + autosize: true, + width: undefined, + }; + return ( + + + + + + ); + } + + const panel = panels.find((p) => p.metric === id); + if (!panel) return null; + return ( + + + + ); + })} + + + ); } +SortableCard.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + gridColumn: PropTypes.string, + plotData: PropTypes.array.isRequired, + plotLayout: PropTypes.object, + children: PropTypes.node.isRequired, +}; + +SortableCard.defaultProps = { + gridColumn: undefined, +}; + ResultsGraphsPlot.propTypes = { - selectedChart: PropTypes.string.isRequired, chartData: PropTypes.object.isRequired, + onToggleRun: PropTypes.func.isRequired, + sessionId: PropTypes.number, }; export default ResultsGraphsPlot; diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx deleted file mode 100644 index a3899d410..000000000 --- a/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { - Box, - ToggleButton, - ToggleButtonGroup, - Typography, -} from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import { useTranslation } from "react-i18next"; - -function ResultsGraphsSelection({ selectedChart, handleChangeChart }) { - const { t } = useTranslation(["models"]); - const theme = useTheme(); - - return ( - - - {t("models:label.chartType", "Chart type")} - - - { - if (v) handleChangeChart(v); - }} - size="small" - > - {t("models:label.bar")} - {t("models:label.heatmap")} - - - ); -} - -ResultsGraphsSelection.propTypes = { - selectedChart: PropTypes.string.isRequired, - handleChangeChart: PropTypes.func.isRequired, -}; - -export default ResultsGraphsSelection; diff --git a/DashAI/front/src/pages/results/constants/graphsMaking.jsx b/DashAI/front/src/pages/results/constants/graphsMaking.jsx index c069f1896..f0e8f049d 100644 --- a/DashAI/front/src/pages/results/constants/graphsMaking.jsx +++ b/DashAI/front/src/pages/results/constants/graphsMaking.jsx @@ -1,17 +1,4 @@ -const getTraceColors = (theme) => [ - theme.palette.primary.main, - theme.palette.secondary.main, - ...(theme.palette.chart?.palette || [ - "#66bb6a", - "#42a5f5", - "#ff9800", - "#ab47bc", - "#ef5350", - "#26a69a", - "#8d6e63", - "#78909c", - ]), -]; +import { getTraceColors } from "../../../utils/chartColors"; /** * Append one run's bar trace to graphsToView. @@ -41,6 +28,96 @@ function graphsMaking(graphsToView, run, metrics, values, runIndex, theme) { return graphsToView; } +/** + * Build one small bar chart PER METRIC (small multiples), instead of a single + * chart with all metrics grouped on one x-axis. Each metric gets its own + * scale, so metrics with very different ranges (e.g. Accuracy vs LogLoss) + * are never forced onto a shared axis. Every run keeps the same color across + * every panel (identity, not rank) so it stays recognizable throughout. + * + * Runs in `hiddenRunIds` are dropped from the plotted bars/axis but still + * appear (dimmed) in the returned `legend`, so clicking them again can bring + * them back — colors are assigned from the FULL run list first, so a run's + * color never shifts when other runs are toggled off/on. + * + * @param {object[]} finishedRuns Array of completed run objects + * @param {Set} hiddenRunIds Run ids currently deselected + * @param {string[]} metrics Metric names — one panel each + * @param {string} metricsKey e.g. "test_metrics" + * @param {object} theme MUI theme object + * @param {object} [metricsMetadata={}] { MetricName: { maximize: bool } } + * @returns {{ panels: object[], legend: {id: number, label: string, color: string, hidden: boolean}[] }} + */ +function smallMultiplesMaking( + finishedRuns, + hiddenRunIds, + metrics, + metricsKey, + theme, + metricsMetadata = {}, +) { + const MAX_LABEL = 16; + const truncate = (s) => + s.length > MAX_LABEL ? `${s.slice(0, MAX_LABEL)}…` : s; + + const colors = getTraceColors(theme); + const fullRunLabels = finishedRuns.map( + (run, idx) => run.run_name || run.name || `Run ${idx + 1}`, + ); + const runColors = finishedRuns.map((_, idx) => colors[idx % colors.length]); + + const visible = finishedRuns + .map((run, idx) => ({ run, idx })) + .filter(({ run }) => !hiddenRunIds.has(run.id)); + + // Use numeric slots (not the run name) as the category axis. Two different + // runs of the same model (e.g. "BaggingClassifier_1"/"_2") often share the + // same truncated prefix — if the label itself were the category value, + // Plotly would treat them as the same category and merge their bars. + const yValues = visible.map((_, i) => i); + const visibleLabels = visible.map(({ idx }) => fullRunLabels[idx]); + const visibleTicks = visibleLabels.map(truncate); + const visibleColors = visible.map(({ idx }) => runColors[idx]); + + const panels = metrics.map((metric) => { + const isInverse = metricsMetadata[metric]?.maximize === false; + const values = visible.map(({ run }) => { + const metricsObj = run[metricsKey] ?? {}; + const v = metricsObj[metric]; + if (v === undefined || v === null) return null; + if (Array.isArray(v)) return v[v.length - 1]?.value ?? null; + return typeof v === "number" ? v : null; + }); + + return { + metric, + title: isInverse ? `${metric} ↓` : metric, + data: [ + { + type: "bar", + orientation: "h", + y: yValues, + x: values, + customdata: visibleLabels, + marker: { color: visibleColors, opacity: 0.85 }, + hovertemplate: "%{customdata}
%{x:.4f}", + }, + ], + }; + }); + + const legend = finishedRuns.map((run, idx) => ({ + id: run.id, + label: fullRunLabels[idx], + color: runColors[idx], + hidden: hiddenRunIds.has(run.id), + })); + + const yaxis = { tickvals: yValues, ticktext: visibleTicks }; + + return { panels, legend, yaxis }; +} + /** * Build a single Plotly heatmap trace from all runs at once. * @@ -52,6 +129,7 @@ function graphsMaking(graphsToView, run, metrics, values, runIndex, theme) { * Colorscale: orange (primary) = worst → green (secondary) = best. * * @param {object[]} finishedRuns Array of completed run objects + * @param {Set} hiddenRunIds Run ids currently deselected * @param {string[]} metrics Metric names (x-axis columns) * @param {string} metricsKey e.g. "test_metrics" * @param {object} theme MUI theme object @@ -60,6 +138,7 @@ function graphsMaking(graphsToView, run, metrics, values, runIndex, theme) { */ function heatmapMaking( finishedRuns, + hiddenRunIds, metrics, metricsKey, theme, @@ -69,12 +148,14 @@ function heatmapMaking( const truncate = (s) => s.length > MAX_LABEL ? `${s.slice(0, MAX_LABEL)}…` : s; - const runLabels = finishedRuns.map((run, idx) => + const visibleRuns = finishedRuns.filter((run) => !hiddenRunIds.has(run.id)); + + const runLabels = visibleRuns.map((run, idx) => truncate(run.run_name || run.name || `Run ${idx + 1}`), ); // Raw values matrix [runs × metrics] - const zRaw = finishedRuns.map((run) => { + const zRaw = visibleRuns.map((run) => { const metricsObj = run[metricsKey] ?? {}; return metrics.map((m) => { const v = metricsObj[m]; @@ -101,7 +182,7 @@ function heatmapMaking( }); }); // Transpose back to [runs × metrics] - const zNorm = finishedRuns.map((_, rIdx) => + const zNorm = visibleRuns.map((_, rIdx) => metrics.map((_, mIdx) => zColorByCol[mIdx][rIdx]), ); @@ -151,5 +232,5 @@ function heatmapMaking( ]; } -export { heatmapMaking }; +export { heatmapMaking, smallMultiplesMaking }; export default graphsMaking; diff --git a/DashAI/front/src/types/artifact.ts b/DashAI/front/src/types/artifact.ts new file mode 100644 index 000000000..25c39f187 --- /dev/null +++ b/DashAI/front/src/types/artifact.ts @@ -0,0 +1,14 @@ +/** + * Typed render artifact returned by explainer plot and explorer results + * endpoints. Payload shape depends on `type`: + * - "plotly": JSON string of a plotly figure. + * - "table": { columns: string[], rows: unknown[][], highlight: {row, column}[] }. + * - "text": plain string. + * - "image": { data: base64 string, mime: string }. + */ +export interface IArtifact { + type: string; + payload: unknown; + title: string | null; + role?: "input" | "explanation"; +} diff --git a/DashAI/front/src/types/component.ts b/DashAI/front/src/types/component.ts index 9bedf444b..f7fad0a32 100644 --- a/DashAI/front/src/types/component.ts +++ b/DashAI/front/src/types/component.ts @@ -10,4 +10,5 @@ export interface IComponent { description: string; display_name?: string; color?: string; + downloaded?: boolean; } diff --git a/DashAI/front/src/types/explorer.ts b/DashAI/front/src/types/explorer.ts index 21ec81b3e..184c3eb6b 100644 --- a/DashAI/front/src/types/explorer.ts +++ b/DashAI/front/src/types/explorer.ts @@ -21,9 +21,3 @@ export enum ExplorerStatus { FINISHED, ERROR, } - -export interface IExplorerResults { - type: string; - data: object; - config: object; -} diff --git a/DashAI/front/src/types/task.ts b/DashAI/front/src/types/task.ts index 3bca2b158..33e17cfe2 100644 --- a/DashAI/front/src/types/task.ts +++ b/DashAI/front/src/types/task.ts @@ -11,4 +11,6 @@ export interface ITaskMetadataParameters { outputs_columns: string[]; inputs_cardinality: "n" | number; outputs_cardinality: "n" | number; + requires_download?: boolean; + download_size_bytes?: number | null; } diff --git a/DashAI/front/src/utils/artifactVisualizerData.jsx b/DashAI/front/src/utils/artifactVisualizerData.jsx new file mode 100644 index 000000000..11c2d7918 --- /dev/null +++ b/DashAI/front/src/utils/artifactVisualizerData.jsx @@ -0,0 +1,124 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Tooltip, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +/** + * Keys of the visualizers used to render explorer results. + */ +export const visualizersKeys = { + tabular: "tabular", + plotly_json: "plotly_json", + image_base64: "image_base64", + image_url: "image_url", +}; + +/** + * NullCell component to render null values in the tabular visualizer + */ +function NullCell() { + const [hover, setHover] = useState(false); + const { t } = useTranslation(["common"]); + return ( + setHover(true)} + onMouseLeave={() => setHover(false)} + > + + {hover ? t("common:none") : "-"} + + + ); +} + +NullCell.propTypes = {}; + +const buildTableColumn = (field, headerName) => ({ + field, + headerName, + renderCell: (params) => { + if (params.value === null) { + return ; + } else if (typeof params.value === "object") { + const tooltip = JSON.stringify(params.value); + return ( + + + {JSON.stringify(params.value)} + + + ); + } else if ( + params.value !== "" && + !isNaN(params.value) && + !Number.isInteger(params.value) + ) { + const tooltip = params.value; + const display = parseFloat(params.value).toFixed(2); + return ( + + {display} + + ); + } + const tooltip = params.value; + return ( + + {params.value} + + ); + }, +}); + +/** + * Convert a typed artifact ({type, payload, title}) returned by the backend + * into the {dataType, data} pair consumed by the explorer visualizers + * (TabularVisualizer, PlotlyJsonVisualizer, ImageVisualizer). + * @param {Object} artifact The artifact to convert + * @returns {{dataType: string, data: any}} + */ +export function artifactToVisualizerData(artifact) { + switch (artifact?.type) { + case "plotly": { + const figure = + typeof artifact.payload === "string" + ? JSON.parse(artifact.payload) + : artifact.payload; + return { dataType: visualizersKeys.plotly_json, data: figure }; + } + case "table": { + const { columns = [], rows = [] } = artifact.payload ?? {}; + const gridColumns = columns.map((column) => + buildTableColumn(column, column === "index" ? "Index" : column), + ); + const gridRows = rows.map((row, rowIndex) => { + const gridRow = { id: rowIndex }; + columns.forEach((column, columnIndex) => { + gridRow[column] = row[columnIndex]; + }); + return gridRow; + }); + return { + dataType: visualizersKeys.tabular, + data: { columns: gridColumns, rows: gridRows }, + }; + } + case "image": { + const { data = "", mime = "image/png" } = artifact.payload ?? {}; + return { + dataType: visualizersKeys.image_url, + data: `data:${mime};base64,${data}`, + }; + } + default: + throw new Error( + `No visualizer found for artifact type: ${artifact?.type}`, + ); + } +} diff --git a/DashAI/front/src/utils/chartColors.js b/DashAI/front/src/utils/chartColors.js new file mode 100644 index 000000000..d6a1fb3e0 --- /dev/null +++ b/DashAI/front/src/utils/chartColors.js @@ -0,0 +1,32 @@ +/** + * Ordered per-series colors for run/metric comparison charts (session + * results, live metrics). Interleaves hues instead of listing primary and + * secondary back-to-back — both are blue, so the first two series were + * nearly indistinguishable when adjacent. + */ +export const getTraceColors = (theme) => { + const [green, blueLight, orange, purple, red, teal, brown, blueGrey] = theme + .palette.chart?.palette || [ + "#66bb6a", + "#42a5f5", + "#ff9800", + "#ab47bc", + "#ef5350", + "#26a69a", + "#8d6e63", + "#78909c", + ]; + + return [ + theme.palette.primary.main, // blue + orange, + green, + purple, + red, + teal, + brown, + theme.palette.secondary.main, // blue (darker) — kept far from the first blue + blueLight, // blue (lighter) — same + blueGrey, + ]; +}; diff --git a/DashAI/front/src/utils/downloadArtifact.js b/DashAI/front/src/utils/downloadArtifact.js new file mode 100644 index 000000000..22f03c52e --- /dev/null +++ b/DashAI/front/src/utils/downloadArtifact.js @@ -0,0 +1,105 @@ +/** + * Escape one CSV cell: wrap in quotes when it contains a comma, quote, or + * newline, doubling embedded quotes. null/undefined become an empty cell. + */ +function escapeCsvCell(value) { + if (value === null || value === undefined) return ""; + const str = typeof value === "object" ? JSON.stringify(value) : String(value); + if (/[",\r\n]/.test(str)) { + return `"${str.replace(/"/g, '""')}"`; + } + return str; +} + +/** + * Build a CSV string from a table artifact payload ({columns, rows}). + */ +export function artifactToCsv(payload) { + const { columns = [], rows = [] } = payload ?? {}; + const lines = [columns.map(escapeCsvCell).join(",")]; + rows.forEach((row) => { + lines.push(row.map(escapeCsvCell).join(",")); + }); + return lines.join("\r\n"); +} + +/** + * Trigger a browser download of a Blob under the given filename. + */ +function triggerDownload(blob, filename) { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +/** + * Sanitize a title into a safe file basename; fall back to "artifact". + */ +function baseName(title) { + const cleaned = (title || "artifact") + .toString() + .trim() + .replace(/[^a-z0-9_-]+/gi, "_") + .replace(/^_+|_+$/g, ""); + return cleaned || "artifact"; +} + +/** + * Download an artifact in a format matching its type. For "plotly", pass the + * rendered plot DOM element in opts.plotEl and the desired image format. + */ +export function downloadArtifact(artifact, opts = {}) { + const name = baseName(artifact.title); + switch (artifact.type) { + case "plotly": { + if (!opts.plotEl) return; + const format = opts.format || "png"; + // Plotly is attached to window by react-plotly.js/plotly.js. + window.Plotly.downloadImage(opts.plotEl, { + format, + filename: name, + height: 800, + width: 1200, + scale: 2, + }); + return; + } + case "table": { + const csv = artifactToCsv(artifact.payload); + triggerDownload( + new Blob([csv], { type: "text/csv;charset=utf-8" }), + `${name}.csv`, + ); + return; + } + case "image": { + const { data = "", mime = "image/png" } = artifact.payload ?? {}; + const ext = mime.split("/")[1] || "png"; + const byteChars = atob(data); + const byteNums = new Array(byteChars.length); + for (let i = 0; i < byteChars.length; i += 1) + byteNums[i] = byteChars.charCodeAt(i); + triggerDownload( + new Blob([new Uint8Array(byteNums)], { type: mime }), + `${name}.${ext}`, + ); + return; + } + case "text": + default: { + const text = + typeof artifact.payload === "string" + ? artifact.payload + : JSON.stringify(artifact.payload, null, 2); + triggerDownload( + new Blob([text], { type: "text/plain;charset=utf-8" }), + `${name}.txt`, + ); + } + } +} diff --git a/DashAI/front/src/utils/downloadArtifact.test.js b/DashAI/front/src/utils/downloadArtifact.test.js new file mode 100644 index 000000000..4b5f4bfaf --- /dev/null +++ b/DashAI/front/src/utils/downloadArtifact.test.js @@ -0,0 +1,17 @@ +import { artifactToCsv } from "./downloadArtifact"; + +test("artifactToCsv builds header and rows, quoting cells with commas", () => { + const csv = artifactToCsv({ + columns: ["feature", "value"], + rows: [ + ["age", 42], + ["city, state", "NY, US"], + ], + }); + expect(csv).toBe('feature,value\r\nage,42\r\n"city, state","NY, US"'); +}); + +test("artifactToCsv renders null as empty cell", () => { + const csv = artifactToCsv({ columns: ["a", "b"], rows: [[null, 1]] }); + expect(csv).toBe("a,b\r\n,1"); +}); diff --git a/DashAI/front/src/utils/i18n/locales/de/common.json b/DashAI/front/src/utils/i18n/locales/de/common.json index f11216b06..06729b236 100644 --- a/DashAI/front/src/utils/i18n/locales/de/common.json +++ b/DashAI/front/src/utils/i18n/locales/de/common.json @@ -13,6 +13,7 @@ "availableItems": "Verfügbare Elemente", "back": "Zurück", "cancel": "Abbrechen", + "clear": "Löschen", "close": "Schließen", "columns": "Spalten", "configureSubmodel": "Teilmodell konfigurieren", @@ -112,6 +113,8 @@ "selectTask": "Aufgabe auswählen", "send": "Senden", "sending": "Wird gesendet...", + "runInformation": "Ausführungsinformationen", + "session": "Sitzung", "sessionInformation": "Sitzungsinformationen", "sessions": "Sitzungen", "skipTour": "Tour überspringen", @@ -141,6 +144,8 @@ "type": "Typ", "unknown": "Unbekannt", "unknownError": "Unbekannter Fehler", + "yes": "Ja", + "no": "Nein", "update": "Aktualisieren", "upload": "Hochladen", "user": "Benutzer", @@ -160,6 +165,18 @@ "columnNameInvalidCharacters": "Spaltenname darf nur Buchstaben, Zahlen und Unterstriche enthalten", "columnNameAlreadyExists": "Eine Spalte mit diesem Namen existiert bereits", "errorRenamingColumn": "Fehler beim Umbenennen der Spalte", + "componentDownload": { + "download": "Herunterladen ({{size}})", + "delete": "Download loeschen", + "deleteWithSize": "Download loeschen ({{size}})", + "downloading": "Wird heruntergeladen...", + "done": "Komponente heruntergeladen", + "deleted": "Download geloescht", + "failed": "Download der Komponente fehlgeschlagen", + "mustDownload": "Dieses Modell muss vor der Nutzung heruntergeladen werden", + "mustDownloadNested": "Diese Komponenten müssen zuerst heruntergeladen werden: {{names}}", + "confirmDelete": "Die heruntergeladenen Dateien für {{name}} löschen? Du kannst es später erneut herunterladen." + }, "jobQueue": { "title": "Aufgabenwarteschlange", "refresh": "Aktualisieren", @@ -186,6 +203,7 @@ "jobType": "Aufgabentyp", "lastUpdated": "Zuletzt aktualisiert", "errorMessage": "Fehlermeldung", + "progress": "Fortschritt", "unnamedJob": "Unbenannte Aufgabe" }, "status": { diff --git a/DashAI/front/src/utils/i18n/locales/de/custom.json b/DashAI/front/src/utils/i18n/locales/de/custom.json index cda354d45..3e848d208 100644 --- a/DashAI/front/src/utils/i18n/locales/de/custom.json +++ b/DashAI/front/src/utils/i18n/locales/de/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Element auswählen, um die Beschreibung zu sehen.", "selectInferenceMethods": "Wählen Sie die anzuwendenden Inferenzmethoden", "search": "Suchen", + "modelSize": "Größe: {{size}}", "noItemsFound": "Keine Komponenten gefunden", "tryAdjustingSearch": "Versuchen Sie, Ihre Suche oder Filter anzupassen", "componentsAvailable_one": "{{count}} Komponente verfügbar", diff --git a/DashAI/front/src/utils/i18n/locales/de/datasets.json b/DashAI/front/src/utils/i18n/locales/de/datasets.json index 8d40a9a87..7d32a0075 100644 --- a/DashAI/front/src/utils/i18n/locales/de/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/de/datasets.json @@ -93,7 +93,8 @@ "requiresExactColumns_other": "Erfordert genau {{required}} gültige Spalten, aber {{available}} verfügbar.", "requiresMinColumns_one": "Erfordert mindestens {{required}} gültige Spalte, aber nur {{available}} verfügbar.", "requiresMinColumns_other": "Erfordert mindestens {{required}} gültige Spalten, aber nur {{available}} verfügbar.", - "zipContentsNotCompatible": "ZIP enthält keine mit dem ausgewählten Datenlader kompatiblen Dateien" + "zipContentsNotCompatible": "ZIP enthält keine mit dem ausgewählten Datenlader kompatiblen Dateien", + "cannotSaveEmptyDataset": "Dataset kann nicht gespeichert werden: Alle Spalten wurden entfernt." }, "label": { "task": "Aufgabe", @@ -254,6 +255,11 @@ "margins": "Ränder", "marginTop": "Oberer Rand", "markerColor": "Markierungsfarbe", + "lineColor": "Linienfarbe", + "increasingColor": "Farbe für Anstieg", + "decreasingColor": "Farbe für Rückgang", + "totalsColor": "Farbe für Summen", + "transparent": "Transparent", "max": "Max", "maxLength": "Maximale Länge", "mean": "Mittelwert", @@ -439,7 +445,11 @@ "showAll": "Alle anzeigen", "hideAll": "Alle ausblenden", "noRows": "Keine Zeilen", + "loadingMore": "Weitere werden geladen…", + "rowsLoaded_one": "{{count}} von {{total}} Zeile geladen", + "rowsLoaded_other": "{{count}} von {{total}} Zeilen geladen", "doubleClickToRename": "Doppelklick zum Umbenennen", + "predictedColumn": "Vorhersage", "sortedAsc": "Aufsteigend sortiert - Klick für absteigend", "sortedDesc": "Absteigend sortiert - Klick zum Entfernen", "clickToSort": "Klick zum aufsteigenden Sortieren", diff --git a/DashAI/front/src/utils/i18n/locales/de/explainers.json b/DashAI/front/src/utils/i18n/locales/de/explainers.json index cd792d738..85b9c49b0 100644 --- a/DashAI/front/src/utils/i18n/locales/de/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/de/explainers.json @@ -3,7 +3,14 @@ "addExplainer": "Erklärungsmodell hinzufügen", "backToExplainers": "Zurück zu Erklärungsmodellen", "hidePlot": "Plot ausblenden", - "showPlot": "Plot anzeigen" + "showPlot": "Plot anzeigen", + "download": "Herunterladen", + "downloadPng": "Als PNG herunterladen", + "downloadSvg": "Als SVG herunterladen", + "editPlot": "Diagramm bearbeiten", + "resetPlot": "Bearbeitungen zuruecksetzen", + "fullscreen": "Vollbild", + "close": "Schließen" }, "error": { "explainerFailed": "Erklärungsmodell konnte nicht generiert werden.", @@ -54,9 +61,23 @@ "selectDatasetToExplain": "Datensatz mit zu erklärenden Instanzen auswählen", "selectExplainer": "Name und Erklärungsmodell festlegen", "selectExplainerAndName": "Erklärungsmodell auswählen und Namen eingeben", + "selectInstance": "Instanz auswählen", "explainerInProgress": "Erklärungsmodell wird verarbeitet...", "splitSelectionSummary": "Prozentsatz: {{percentage}}% | Ausgewählte Zeilen: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Erklärungsmodelle suchen..." + "searchExplainers": "Erklärungsmodelle suchen...", + "sourceFromDataset": "Aus Datensatz", + "sourceManualInput": "Manuell eingeben", + "enterInstancesManually": "Geben Sie die zu erklärenden Instanzen ein", + "manualInputDescription": "Geben Sie die Merkmalswerte für jede zu erklärende Instanz ein.", + "explanationInfo": "Erklärungsinformationen", + "inputColumns": "Eingabespalten:", + "targetColumn": "Zielspalte:", + "markRowsToExplain": "Markieren Sie die zu erklärenden Zeilen", + "rowSelectionMode": "Zeilenauswahlmodus", + "rowModePercentage": "Prozent-Schieberegler", + "rowModeManual": "Manuelle Auswahl", + "shuffleRows": "Ausgewählte Zeilen mischen (Zufallsstichprobe)", + "rowsSelectedManually": "Manuell ausgewählte Zeilen: {{selected}} / {{total}}" }, "message": { "explainerJobCompleted": "Erklärungsmodell {{name}} erfolgreich abgeschlossen", diff --git a/DashAI/front/src/utils/i18n/locales/de/generative.json b/DashAI/front/src/utils/i18n/locales/de/generative.json index 2446ff33d..bf88e144f 100644 --- a/DashAI/front/src/utils/i18n/locales/de/generative.json +++ b/DashAI/front/src/utils/i18n/locales/de/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Name ist erforderlich", "processError": "Der Prozess ist fehlgeschlagen. Wird gelöscht... {{error}}", "sessionNameEmpty": "Sitzungsname darf nicht leer sein", - "sessionNameExists": "Eine Sitzung mit diesem Namen existiert bereits" + "sessionNameExists": "Eine Sitzung mit diesem Namen existiert bereits", + "modelSwitchFailed": "Sitzungsmodell konnte nicht geändert werden" }, "label": { "confirmDeleteSession": "Sind Sie sicher, dass Sie die Sitzung \"{{name}}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", @@ -38,6 +39,8 @@ "startBySelectingATask": "Beginnen Sie mit der Auswahl einer generativen Aufgabe.", "noSessionsFound": "Keine Sitzungen gefunden", "parameterChangeEvent": "Parameter aktualisiert: <1>", + "modelChangeEvent": "Modell geändert: <1>", + "modelNotDownloaded": "Das Modell dieser Sitzung ist noch nicht heruntergeladen. Laden Sie es herunter, um fortzufahren.", "parameterChangeHistory": "Parameteränderungsverlauf für die aktuelle Sitzung", "searchSessions": "Sitzungen suchen", "selectGenerativeTask": "Generative Aufgabe für neue Sitzung auswählen", @@ -57,7 +60,9 @@ "attachMedia": "Medien anhängen", "attachMediaToContinue": "Medien anhängen, um fortzufahren", "noInputAvailable": "Keine Eingabe für diese Aufgabe verfügbar", - "selectModelCrumb": "Modell auswählen" + "selectModelCrumb": "Modell auswählen", + "sessionModel": "Modell", + "downloadRequired": "Download erforderlich" }, "message": { "sessionCreatedSuccess": "Sitzung erfolgreich erstellt.", diff --git a/DashAI/front/src/utils/i18n/locales/de/models.json b/DashAI/front/src/utils/i18n/locales/de/models.json index c79b662c4..07dbcc83d 100644 --- a/DashAI/front/src/utils/i18n/locales/de/models.json +++ b/DashAI/front/src/utils/i18n/locales/de/models.json @@ -5,6 +5,7 @@ "createLocalExplainer": "Neues lokales Erklärungsmodell", "createPrediction": "Vorhersage erstellen", "createSession": "Sitzung erstellen", + "addNewPrediction": "Neue Vorhersage hinzufügen", "deleteAndRetrain": "Löschen und neu trainieren", "deleteRun": "Durchlauf löschen", "hideOperations": "Operationen ausblenden", @@ -61,14 +62,22 @@ "label": { "addModelToSession": "Modell zur Sitzung hinzufügen", "confirmDeleteSession": "Sind Sie sicher, dass Sie die Sitzung \"{{name}}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "availableExplainers": "Verfügbare Erklärungsmodelle", "availableModels": "Verfügbare Modelle", + "modelCount_one": "{{count}} Modell", + "modelCount_other": "{{count}} Modelle", "bar": "Balken", "heatmap": "Heatmap", "chooseTaskForSessionWithDataset": "Wählen Sie die ML-Aufgabe für Ihre Sitzung mit dem Datensatz \"{{datasetName}}\".", + "configuration": "Konfiguration", + "modelConfiguration": "Modellkonfiguration", + "sessionConfiguration": "Sitzungskonfiguration", "configureModel": "Modell konfigurieren", "configureOptimizer": "Optimierer konfigurieren", "configureSession": "Sitzung konfigurieren", "configureTasksTrainCompareModels": "Aufgaben konfigurieren, Modelle trainieren und vergleichen. Wählen Sie eine Aufgabe, um Ihren Modellierungs-Workflow zu beginnen.", + "inputColumns": "Eingabespalten", + "outputColumns": "Ausgabespalten", "customMetrics": "Benutzerdefinierte Metriken", "datasetPredictions": "Datensatz-Vorhersagen", "editRunParameters": "Parameter bearbeiten und Modell neu ausführen", @@ -91,25 +100,32 @@ "localExplainer": "Lokales Erklärungsmodell", "localExplainers": "Lokale Erklärungsmodelle", "manualPredictions": "Manuelle Vorhersagen", + "manual": "Manuell", "metricsEmptyForDisplaySet": "Die Ergebnismetriken für {{set}} sind leer.", "metrics": "Metriken", "metricToOptimize": "Zu optimierende Metrik", + "searchMetric": "Metrik suchen...", "modelComparison": "Modellvergleich", "modelsModule": "Modell-Modul", "nameYourSession": "Sitzung benennen", "selectDatasetAndPrepare": "Benennen Sie Ihre Sitzung, wählen Sie einen Datensatz aus und konfigurieren Sie Spalten und Aufteilungen.", + "noCompatibleExplainersFound": "Keine kompatiblen Erklärungsmodelle gefunden", "noCompatibleModelsFound": "Keine kompatiblen Modelle gefunden", + "noConfigurationAvailable": "Keine Konfiguration für diesen Durchlauf verfügbar", "dropModelHere": "Hier ablegen zum Hinzufügen", + "noCompletedRuns": "Noch keine abgeschlossenen Durchläufe", "noDatasetPredictionsYet": "Noch keine Datensatz-Vorhersagen", "noGlobalExplainersYet": "Noch keine globalen Erklärungsmodelle", "noLocalExplainersYet": "Noch keine lokalen Erklärungsmodelle", "noManualPredictionsYet": "Noch keine manuellen Vorhersagen", "noMetricsAvailable": "Keine Metriken verfügbar", "noMetricsAvailableForThisView": "Keine Metriken für diese Ansicht verfügbar", + "noExplainersMatchSearch": "Keine Erklärungsmodelle entsprechen Ihrer Suche", "noModelsMatchSearch": "Keine Modelle entsprechen Ihrer Suche", "noPredictionsYet": "Noch keine Vorhersagen", "noRunsYet": "Noch keine Durchläufe. Fügen Sie Modelle aus dem rechten Panel hinzu.", "noSessionSelected": "Keine Sitzung ausgewählt", + "operations": "Vorgänge", "operationsWillBeDeletedWarning": "Diese Operationen werden dauerhaft gelöscht und können nicht wiederhergestellt werden. Sind Sie sicher?", "optimizerConfiguration": "Einstellungen des Hyperparameter-Optimierers konfigurieren", "optimizerParameters": "Optimiererparameter", @@ -125,9 +141,11 @@ "retrainWillDeleteOperations": "Dieser Durchlauf hat bestehende Operationen, die gelöscht werden", "retrainWillDeleteOperationsDetails": "Das Neu-Trainieren von \"<1>{{runName}}\" löscht:", "saveParameterChanges": "Parameteränderungen speichern?", + "saveConfirmDetails": "Das Speichern von \"<1>{{runName}}\" setzt seinen Status auf 'Nicht gestartet' zurück und löscht seine aktuellen Metriken und Ergebnisse. Möchten Sie wirklich fortfahren?", "showResults": "Ergebnisse anzeigen", "saveWillDeleteOperationsDetails": "Das Speichern von \"<1>{{runName}}\" setzt den Durchlauf zurück. Folgendes wird beim erneuten Training gelöscht:", "runDetails": "Durchlaufdetails", + "runNotFound": "Durchlauf nicht gefunden", "runExperimentToSeeMetrics": "Gehen Sie zum<1>Experiments-Tab, um Ihr Experiment auszuführen.", "runFailedNoHyperparameterPlots": "Durchlauf fehlgeschlagen. Keine Hyperparameter-Plots verfügbar.", "runInProgressCannotEdit": "Der Durchlauf wird gerade ausgeführt und kann nicht bearbeitet werden.", @@ -137,6 +155,7 @@ "searchModels": "Modelle suchen...", "selectDataset": "Datensatz auswählen", "selectDatasetForSession": "Datensatz für Ihre Sitzung auswählen", + "exitModelDetailToAddModels": "Kehren Sie zur Sitzungsübersicht zurück, um weitere Modelle hinzuzufügen.", "selectSessionToViewModels": "Wählen Sie eine Sitzung, um verfügbare Modelle anzuzeigen.", "selectTask": "Aufgabe auswählen", "selectTaskForSession": "Eine Aufgabe für Ihre Sitzung auswählen", @@ -158,26 +177,14 @@ "higherIsBetter": "Höher ist besser", "lowerIsBetter": "Niedriger ist besser", "bestModel": "Bestes", - "score": "Wertung", - "scoreProfile": "Bewertungsprofil", - "scoreHeaderTooltip": "Gewichtete Wertung 0–100 basierend auf dem ausgewählten Profil. Unbegrenzte Fehlermetriken (MAE, RMSE, TER, …) werden relativ zum schlechtesten Modell normalisiert. Fahren Sie über eine Zelle, um die Aufschlüsselung zu sehen.", - "profile_balanced": "Ausgewogen", - "profile_detectPositives": "Positive erkennen", - "profile_avoidFalseAlarms": "Falschalarme vermeiden", - "profile_probabilityQuality": "Wahrscheinlichkeitsqualität", - "profile_regression_fit": "Modellanpassung", - "profile_regression_error": "Fehler ausgewogen", - "profile_translation_quality": "Übersetzungsqualität", - "profile_translation_balanced": "Übersetzung ausgewogen", - "profile_text_balanced": "Ausgewogen", - "profile_text_detectPositives": "Positive erkennen", - "profile_text_avoidFalseAlarms": "Falschalarme vermeiden", - "profile_text_probabilityQuality": "Wahrscheinlichkeitsqualität", "editRun": "Durchlauf bearbeiten", "chartType": "Diagrammtyp" }, "message": { + "skippedUndownloadedRuns": "Durchläufe mit nicht heruntergeladenem Modell wurden übersprungen. Lade das Modell zuerst herunter.", "allRunsCompleted": "{{experiment}} hat alle Durchläufe abgeschlossen.", + "tabAvailableAfterFinish": "Verfügbar, sobald der Durchlauf das Training abgeschlossen hat.", + "noOptimizableParamsForHpo": "Keine Parameter sind für die Optimierung markiert — bearbeiten Sie die Konfiguration des Durchlaufs, um dies zu aktivieren.", "confirmDeleteRun": "Sind Sie sicher, dass Sie diesen Durchlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "editingParametersWarning": "Das Bearbeiten von Parametern setzt den Status dieses Durchlaufs auf 'Nicht gestartet' zurück. Alle vorhandenen Metriken und Ergebnisse gehen verloren.", "aboutToUpdateParameters": "Sie sind dabei, die Parameter für Durchlauf {{runName}} zu aktualisieren. Diese Aktion hat folgende Konsequenzen:", diff --git a/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json index 700d26c2c..7ba89c580 100644 --- a/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Ihre Modell-Durchlauf-Karte<1>Diese Karte enthält alle Informationen zu Ihrem Modelldurchlauf. Hier können Sie:<2><0><0>Trainieren: Den Trainingsprozess mit Ihren konfigurierten Parametern starten<1><0>Metriken anzeigen: Leistungswerte nach dem Training einsehen<2><0>Vorhersagen erstellen: Ihr trainiertes Modell auf neue Daten anwenden<3><0>Erklärungsmodelle erstellen: Verstehen, wie Ihr Modell Entscheidungen trifft<3>Trainieren Sie dieses Modell, um seine Leistung zu sehen!", "performanceVisualizations": "<0><0>Leistungsvisualisierungen<1>Die Graphenansicht zeigt Leistungsmetriken und andere Visualisierungen, um die Leistung Ihrer Modelle besser zu verstehen.<2>🎉 <0>Gut gemacht! Sie können jetzt weitere Modelle hinzufügen und mit verschiedenen Parametern experimentieren!", "sessionVisualization": "<0><0>Sitzungsvisualisierung<1>Willkommen in der Sitzungsvisualisierung! Hier können Sie verschiedene Modelle vergleichen, trainieren und ihre Leistung analysieren.<2>Beginnen wir damit, einige Modelle zum Vergleich in dieser Sitzung hinzuzufügen.", - "trainModel": "<0><0>Modell trainieren<1>Klicken Sie auf die Schaltfläche <0>Trainieren, um Ihr Modell mit den konfigurierten Parametern zu trainieren.<2>Der Trainingsprozess läuft im Hintergrund, und Sie können hier den Fortschritt und die Ergebnisse sehen.<3><0>Klicken Sie auf \"Trainieren\", um fortzufahren!", - "visualizeResults": "<0><0>Ergebnisse visualisieren<1>Möchten Sie Ihre Ergebnisse anschaulicher sehen? Klicken Sie auf die Schaltfläche <0>Graphen, um von der Tabellenansicht zu interaktiven Diagrammen zu wechseln.<2><0>Klicken Sie auf \"Graphen\", um fortzufahren!" + "trainModel": "<0><0>Modell trainieren<1>Klicken Sie auf die Schaltfläche <0>Trainieren, um Ihr Modell mit den konfigurierten Parametern zu trainieren.<2>Der Trainingsprozess läuft im Hintergrund, und Sie können hier den Fortschritt und die Ergebnisse sehen.<3><0>Klicken Sie auf \"Trainieren\", um fortzufahren!" } diff --git a/DashAI/front/src/utils/i18n/locales/de/prediction.json b/DashAI/front/src/utils/i18n/locales/de/prediction.json index 0084fe2b5..d302ffdab 100644 --- a/DashAI/front/src/utils/i18n/locales/de/prediction.json +++ b/DashAI/front/src/utils/i18n/locales/de/prediction.json @@ -1,5 +1,7 @@ { "button": { + "deleteSelected_one": "Ausgewählte löschen ({{count}})", + "deleteSelected_other": "Ausgewählte löschen ({{count}})", "downloadResults": "Vorhersageergebnisse herunterladen", "hideResults": "Ergebnisse ausblenden", "runPrediction": "Vorhersage ausführen", @@ -9,6 +11,7 @@ "error": { "creatingPrediction": "Fehler beim Erstellen der Vorhersage", "errorDeleting": "Fehler beim Löschen der Vorhersage", + "errorDeletingSelected": "Fehler beim Löschen der ausgewählten Vorhersagen", "errorDownloading": "Fehler beim Herunterladen der Vorhersage", "fetchingDatasets": "Fehler beim Abrufen der Datensätze für die Vorhersage.", "loadingPredictionData": "Fehler beim Laden der Vorhersagedaten", @@ -25,6 +28,8 @@ "clickToViewOrDelete": "Klicken Sie auf eine Vorhersage, um Details anzuzeigen oder sie mit dem Lösch-Symbol zu löschen.", "configureInput": "Eingabe konfigurieren", "confirm": "Bestätigen", + "confirmBulkDeletion_one": "Möchten Sie die ausgewählte Vorhersage wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmBulkDeletion_other": "Möchten Sie die {{count}} ausgewählten Vorhersagen wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeletion": "Sind Sie sicher, dass Sie diese Vorhersage löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeletionTitle": "Vorhersage löschen?", "confirmPrediction": "Vorhersage bestätigen", @@ -52,7 +57,6 @@ "previewResults": "Ergebnisse in der Vorschau anzeigen", "previousPredictions": "Frühere Vorhersagen", "provideManualInput": "Daten manuell für die Vorhersage eingeben. Füllen Sie die Felder aus und reichen Sie sie ein, wenn Sie bereit sind.", - "resultsPreview": "Vorschau der ersten 100 Zeilen. CSV herunterladen für vollständige Ergebnisse.", "resultsPreviewDownloadInfo": "Die Tabelle zeigt eine Vorschau der Vorhersageergebnisse. Sie können die vollständigen Ergebnisse als CSV-Datei herunterladen.", "reviewDetails": "Überprüfen Sie Ihre Vorhersagekonfiguration vor dem Einreichen", "runFirstPrediction": "Führen Sie Ihre erste Vorhersage aus, um sie hier zu sehen", @@ -62,6 +66,7 @@ "selectDataset": "Datensatz für die Vorhersage auswählen", "selectDatasetFromPlatform": "Datensatz von der Plattform auswählen", "selectMode": "Modus auswählen", + "selectRowsToDelete": "Zeilen zum Löschen auswählen", "selectPredictionMode": "Wählen Sie, wie Sie Eingabedaten für die Vorhersage bereitstellen möchten", "targetColumn": "Zielspalte", "useExistingDataset": "Vorhandenen Datensatz verwenden" @@ -70,6 +75,8 @@ "deletedSuccessfully": "Vorhersage erfolgreich gelöscht", "downloadedSuccessfully": "Vorhersage erfolgreich heruntergeladen", "predictionCompleted": "Vorhersage erfolgreich abgeschlossen", - "predictionJobSubmitted": "Vorhersage-Job erfolgreich eingereicht" + "predictionJobSubmitted": "Vorhersage-Job erfolgreich eingereicht", + "predictionsDeleted_one": "{{count}} Vorhersage erfolgreich gelöscht", + "predictionsDeleted_other": "{{count}} Vorhersagen erfolgreich gelöscht" } } diff --git a/DashAI/front/src/utils/i18n/locales/en/common.json b/DashAI/front/src/utils/i18n/locales/en/common.json index dc3d26b7f..9bdd065c8 100644 --- a/DashAI/front/src/utils/i18n/locales/en/common.json +++ b/DashAI/front/src/utils/i18n/locales/en/common.json @@ -13,6 +13,7 @@ "availableItems": "Available Items", "back": "Back", "cancel": "Cancel", + "clear": "Clear", "close": "Close", "columns": "Columns", "configureSubmodel": "Configure submodel", @@ -112,6 +113,8 @@ "selectTask": "Select task", "send": "Send", "sending": "Sending...", + "runInformation": "Run Information", + "session": "Session", "sessionInformation": "Session Information", "sessions": "Sessions", "skipTour": "Skip Tour", @@ -141,6 +144,8 @@ "type": "Type", "unknown": "Unknown", "unknownError": "Unknown error", + "yes": "Yes", + "no": "No", "update": "Update", "upload": "Upload", "user": "User", @@ -160,6 +165,18 @@ "columnNameInvalidCharacters": "Column name can only contain letters, numbers and underscores", "columnNameAlreadyExists": "A column with this name already exists", "errorRenamingColumn": "Error renaming column", + "componentDownload": { + "download": "Download ({{size}})", + "delete": "Delete download", + "deleteWithSize": "Delete download ({{size}})", + "downloading": "Downloading...", + "done": "Component downloaded", + "deleted": "Download deleted", + "failed": "Component download failed", + "mustDownload": "This model must be downloaded before use", + "mustDownloadNested": "These components must be downloaded first: {{names}}", + "confirmDelete": "Delete the downloaded files for {{name}}? You can download it again later." + }, "jobQueue": { "title": "Job Queue", "refresh": "Refresh", @@ -186,6 +203,7 @@ "jobType": "Job Type", "lastUpdated": "Last Updated", "errorMessage": "Error Message", + "progress": "Progress", "unnamedJob": "Unnamed Job" }, "status": { diff --git a/DashAI/front/src/utils/i18n/locales/en/custom.json b/DashAI/front/src/utils/i18n/locales/en/custom.json index 7ca90eba3..3e8644117 100644 --- a/DashAI/front/src/utils/i18n/locales/en/custom.json +++ b/DashAI/front/src/utils/i18n/locales/en/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Select an item to see the description.", "selectInferenceMethods": "Select the inference methods you want to apply", "search": "Search", + "modelSize": "Size: {{size}}", "noItemsFound": "No components found", "tryAdjustingSearch": "Try adjusting your search or filters", "componentsAvailable_one": "{{count}} component available", diff --git a/DashAI/front/src/utils/i18n/locales/en/datasets.json b/DashAI/front/src/utils/i18n/locales/en/datasets.json index 235558247..9ee7263cc 100644 --- a/DashAI/front/src/utils/i18n/locales/en/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/en/datasets.json @@ -91,7 +91,8 @@ "requiresExactColumns_other": "Requires exactly {{required}} valid columns, but {{available}} available.", "requiresMinColumns_one": "Requires at least {{required}} valid column, but only {{available}} available.", "requiresMinColumns_other": "Requires at least {{required}} valid columns, but only {{available}} available.", - "zipContentsNotCompatible": "ZIP does not contain files compatible with the selected dataloader" + "zipContentsNotCompatible": "ZIP does not contain files compatible with the selected dataloader", + "cannotSaveEmptyDataset": "Cannot save dataset: all columns have been removed." }, "label": { "task": "Task", @@ -254,6 +255,11 @@ "margins": "Margins", "marginTop": "Margin Top", "markerColor": "Marker Color", + "lineColor": "Line Color", + "increasingColor": "Increasing Color", + "decreasingColor": "Decreasing Color", + "totalsColor": "Totals Color", + "transparent": "Transparent", "max": "Max", "maxLength": "Max Length", "mean": "Mean", @@ -433,7 +439,11 @@ "showAll": "Show all", "hideAll": "Hide all", "noRows": "No rows", + "loadingMore": "Loading more…", + "rowsLoaded_one": "{{count}} of {{total}} row loaded", + "rowsLoaded_other": "{{count}} of {{total}} rows loaded", "doubleClickToRename": "Double-click to rename", + "predictedColumn": "Prediction", "sortedAsc": "Sorted ascending - click for descending", "sortedDesc": "Sorted descending - click to clear", "clickToSort": "Click to sort ascending", diff --git a/DashAI/front/src/utils/i18n/locales/en/explainers.json b/DashAI/front/src/utils/i18n/locales/en/explainers.json index d937ff713..00ada09bb 100644 --- a/DashAI/front/src/utils/i18n/locales/en/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/en/explainers.json @@ -3,7 +3,14 @@ "addExplainer": "Add Explainer", "backToExplainers": "Back to explainers", "hidePlot": "Hide Plot", - "showPlot": "Show Plot" + "showPlot": "Show Plot", + "download": "Download", + "downloadPng": "Download as PNG", + "downloadSvg": "Download as SVG", + "editPlot": "Edit plot", + "resetPlot": "Reset edits", + "fullscreen": "Fullscreen", + "close": "Close" }, "error": { "explainerFailed": "Explainer failed to generate.", @@ -54,9 +61,23 @@ "selectDatasetToExplain": "Select a dataset with instances to explain", "selectExplainer": "Set name and explainer", "selectExplainerAndName": "Select a explainer and enter a name", + "selectInstance": "Select an instance", "explainerInProgress": "Explainer in progress...", "splitSelectionSummary": "Percentage: {{percentage}}% | Rows selected: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Search explainers..." + "searchExplainers": "Search explainers...", + "sourceFromDataset": "From dataset", + "sourceManualInput": "Enter manually", + "enterInstancesManually": "Enter the instances to explain", + "manualInputDescription": "Fill in the feature values for each instance you want to explain.", + "explanationInfo": "Explanation Info", + "inputColumns": "Input Columns:", + "targetColumn": "Target Column:", + "markRowsToExplain": "Mark the rows to explain", + "rowSelectionMode": "Row selection mode", + "rowModePercentage": "Percentage slider", + "rowModeManual": "Manual selection", + "shuffleRows": "Shuffle selected rows (random sample)", + "rowsSelectedManually": "Rows selected manually: {{selected}} / {{total}}" }, "message": { "explainerJobCompleted": "Explainer {{name}} completed successfully", diff --git a/DashAI/front/src/utils/i18n/locales/en/generative.json b/DashAI/front/src/utils/i18n/locales/en/generative.json index 654597724..3c2ade281 100644 --- a/DashAI/front/src/utils/i18n/locales/en/generative.json +++ b/DashAI/front/src/utils/i18n/locales/en/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Name is required", "processError": "The process has failed. Deleting it... {{error}}", "sessionNameEmpty": "Session name cannot be empty", - "sessionNameExists": "A session with this name already exists" + "sessionNameExists": "A session with this name already exists", + "modelSwitchFailed": "Failed to change the session model" }, "label": { "confirmDeleteSession": "Are you sure you want to delete the session \"{{name}}\"? This action cannot be undone.", @@ -38,6 +39,8 @@ "startBySelectingATask": "Start by selecting a generative task.", "noSessionsFound": "No sessions found", "parameterChangeEvent": "Parameters updated: <1>", + "modelChangeEvent": "Model changed: <1>", + "modelNotDownloaded": "This session's model is not downloaded yet. Download it to continue.", "parameterChangeHistory": "Parameter change history for the current session", "searchSessions": "Search Sessions", "selectGenerativeTask": "Select a generative task to start a new session", @@ -57,7 +60,9 @@ "attachMedia": "Attach media", "attachMediaToContinue": "Attach media to continue", "noInputAvailable": "No input available for this task", - "selectModelCrumb": "Select Model" + "selectModelCrumb": "Select Model", + "sessionModel": "Model", + "downloadRequired": "download required" }, "message": { "sessionCreatedSuccess": "Session successfully created.", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index 3770033e7..9cc99ddc3 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -5,6 +5,7 @@ "createLocalExplainer": "New Local Explainer", "createPrediction": "Create Prediction", "createSession": "Create Session", + "addNewPrediction": "Add New Prediction", "deleteAndRetrain": "Delete & Re-train", "deleteRun": "Delete Run", "hideOperations": "Hide Operations", @@ -61,14 +62,22 @@ "label": { "addModelToSession": "Add Model to Session", "confirmDeleteSession": "Are you sure you want to delete the session \"{{name}}\"? This action cannot be undone.", + "availableExplainers": "Available Explainers", "availableModels": "Available Models", + "modelCount_one": "{{count}} model", + "modelCount_other": "{{count}} models", "bar": "Bar", "heatmap": "Heatmap", "chooseTaskForSessionWithDataset": "Choose the machine learning task for your session with dataset \"{{datasetName}}\".", "configureModel": "Configure Model", + "configuration": "Configuration", + "modelConfiguration": "Model Configuration", + "sessionConfiguration": "Session Configuration", "configureOptimizer": "Configure Optimizer", "configureSession": "Configure Session", "configureTasksTrainCompareModels": "Configure tasks, train and compare models in organized sessions. Select a task to begin your modeling workflow.", + "inputColumns": "Input columns", + "outputColumns": "Output columns", "customMetrics": "Custom Metrics", "datasetPredictions": "Dataset Predictions", "editRunParameters": "Edit parameters and re-run the model", @@ -91,25 +100,32 @@ "localExplainer": "Local Explainer", "localExplainers": "Local Explainers", "manualPredictions": "Manual Predictions", + "manual": "Manual", "metricsEmptyForDisplaySet": "The result metrics for {{set}} are empty.", "metrics": "Metrics", "metricToOptimize": "Metric to Optimize", + "searchMetric": "Search metric...", "modelComparison": "Model Comparison", "modelsModule": "Models Module", "nameYourSession": "Name Your Session", "selectDatasetAndPrepare": "Name your session, select a dataset and configure its columns and splits.", + "noCompatibleExplainersFound": "No compatible explainers found", "noCompatibleModelsFound": "No compatible models found", + "noConfigurationAvailable": "No configuration available for this run", "dropModelHere": "Drop here to add", + "noCompletedRuns": "No completed runs yet", "noDatasetPredictionsYet": "No dataset predictions yet", "noGlobalExplainersYet": "No global explainers yet", "noLocalExplainersYet": "No local explainers yet", "noManualPredictionsYet": "No manual predictions yet", "noMetricsAvailable": "No metrics available", "noMetricsAvailableForThisView": "No metrics available for this view", + "noExplainersMatchSearch": "No explainers match your search", "noModelsMatchSearch": "No models match your search", "noPredictionsYet": "No predictions yet", "noRunsYet": "No runs yet. Add models from the right panel.", "noSessionSelected": "No Session Selected", + "operations": "Operations", "operationsWillBeDeletedWarning": "These operations will be permanently removed and cannot be recovered. Are you sure you want to continue?", "optimizerConfiguration": "Configure hyperparameter optimizer settings", "optimizerParameters": "Optimizer Parameters", @@ -125,9 +141,11 @@ "retrainWillDeleteOperations": "This run has existing operations that will be deleted", "retrainWillDeleteOperationsDetails": "Re-training run \"<1>{{runName}}\" will delete:", "saveParameterChanges": "Save Parameter Changes?", + "saveConfirmDetails": "Saving \"<1>{{runName}}\" will reset its status to 'Not Started' and clear its current metrics and results. Are you sure you want to continue?", "showResults": "Show Results", "saveWillDeleteOperationsDetails": "Saving \"<1>{{runName}}\" will reset the run. The following will be deleted when you train again:", "runDetails": "Run Details", + "runNotFound": "Run not found", "runExperimentToSeeMetrics": "Go to<1>experiments tabto run your experiment.", "runFailedNoHyperparameterPlots": "Run Failed. No hyperparameter plots available.", "runInProgressCannotEdit": "Run is currently in progress and cannot be edited.", @@ -137,6 +155,7 @@ "searchModels": "Search Models...", "selectDataset": "Select Dataset", "selectDatasetForSession": "Select dataset for your session", + "exitModelDetailToAddModels": "Go back to the session overview to add more models.", "selectSessionToViewModels": "Select a session to view available models.", "selectTask": "Select a Task", "selectTaskForSession": "Select a Task for Your Session", @@ -158,26 +177,14 @@ "higherIsBetter": "Higher is better", "lowerIsBetter": "Lower is better", "bestModel": "Best", - "score": "Score", - "scoreProfile": "Score Profile", - "scoreHeaderTooltip": "Weighted score 0–100 based on the selected profile. Unbounded error metrics (MAE, RMSE, TER, …) are normalized relative to the worst model in this comparison. Hover a cell to see the breakdown.", - "profile_balanced": "Balanced", - "profile_detectPositives": "Detect Positives", - "profile_avoidFalseAlarms": "Avoid False Alarms", - "profile_probabilityQuality": "Probability Quality", - "profile_regression_fit": "Model Fit", - "profile_regression_error": "Error Balanced", - "profile_translation_quality": "Translation Quality", - "profile_translation_balanced": "Translation Balanced", - "profile_text_balanced": "Balanced", - "profile_text_detectPositives": "Detect Positives", - "profile_text_avoidFalseAlarms": "Avoid False Alarms", - "profile_text_probabilityQuality": "Probability Quality", "editRun": "Edit Run", "chartType": "Chart type" }, "message": { + "skippedUndownloadedRuns": "Skipped runs whose model is not downloaded. Download the model first.", "allRunsCompleted": "{{experiment}} has completed all its runs.", + "tabAvailableAfterFinish": "Available once the run finishes training.", + "noOptimizableParamsForHpo": "No parameters are marked for optimization — edit the run's configuration to enable this.", "confirmDeleteRun": "Are you sure you want to delete this run? This action cannot be undone.", "editingParametersWarning": "Editing parameters will reset this run's status to 'Not Started'. All existing metrics and results will be lost.", "aboutToUpdateParameters": "You are about to update the parameters for run {{runName}}. This action will have the following consequences:", diff --git a/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json index 4d176c2d0..95bb8e5e6 100644 --- a/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Your Model Run Card<1>Perfect! This card contains everything about your model run. Here you can:<2><0><0>Train: Start the training process with your configured parameters<1><0>View Metrics: See performance scores once training is complete<2><0>Make Predictions: Use your trained model on new data<3><0>Create Explainers: Understand how your model makes decisions<3>Let's train this model to see how it performs!", "performanceVisualizations": "<0><0>Performance Visualizations<1>The graphs view shows performance metrics and other visualizations to help you better understand your models' performance.<2>🎉 <0>Great job! You can now add more models and experiment with different parameters!", "sessionVisualization": "<0><0>Session Visualization<1>Welcome to the Session Visualization! This is where you can compare different models, train them, and analyze their performance.<2>Let's start by adding some models to compare in this session.", - "trainModel": "<0><0>Train Your Model<1>Click the <0>Train button to start training your model with the configured parameters.<2>The training process will run in the background, and you'll be able to see the progress and results here.<3><0>Click \"Train\" to continue!", - "visualizeResults": "<0><0>Visualize Results<1>Want to see your results in a more visual way? Click the <0>Graphs button to switch from the table view to interactive charts.<2><0>Click \"Graphs\" to continue!" + "trainModel": "<0><0>Train Your Model<1>Click the <0>Train button to start training your model with the configured parameters.<2>The training process will run in the background, and you'll be able to see the progress and results here.<3><0>Click \"Train\" to continue!" } diff --git a/DashAI/front/src/utils/i18n/locales/en/prediction.json b/DashAI/front/src/utils/i18n/locales/en/prediction.json index e1868e2bc..5ebb59600 100644 --- a/DashAI/front/src/utils/i18n/locales/en/prediction.json +++ b/DashAI/front/src/utils/i18n/locales/en/prediction.json @@ -1,5 +1,7 @@ { "button": { + "deleteSelected_one": "Delete Selected ({{count}})", + "deleteSelected_other": "Delete Selected ({{count}})", "downloadResults": "Download Prediction Results", "hideResults": "Hide Results", "runPrediction": "Run Prediction", @@ -9,6 +11,7 @@ "error": { "creatingPrediction": "Error creating prediction", "errorDeleting": "Error deleting prediction", + "errorDeletingSelected": "Error deleting selected predictions", "errorDownloading": "Error downloading prediction", "fetchingDatasets": "Error fetching datasets for prediction.", "loadingPredictionData": "Error loading prediction data", @@ -25,6 +28,8 @@ "clickToViewOrDelete": "Click on a prediction to view details or delete it using the delete icon.", "configureInput": "Configure Input", "confirm": "Confirm", + "confirmBulkDeletion_one": "Are you sure you want to delete the selected prediction? This action cannot be undone.", + "confirmBulkDeletion_other": "Are you sure you want to delete the {{count}} selected predictions? This action cannot be undone.", "confirmDeletion": "Are you sure you want to delete this prediction? This action cannot be undone.", "confirmDeletionTitle": "Delete Prediction?", "confirmPrediction": "Confirm Prediction", @@ -52,7 +57,6 @@ "previewResults": "Preview Results", "previousPredictions": "Previous Predictions", "provideManualInput": "Enter data manually for prediction. Fill in the fields below and submit when ready.", - "resultsPreview": "Preview of first 100 rows. Download CSV for complete results.", "resultsPreviewDownloadInfo": "The table below displays a preview of the prediction results. You can download the full results as a CSV file using the \"Download CSV\" button below.", "reviewDetails": "Review your prediction configuration before submitting", "runFirstPrediction": "Run your first prediction to see it here", @@ -62,6 +66,7 @@ "selectDataset": "Select a dataset for prediction", "selectDatasetFromPlatform": "Select a dataset from platform", "selectMode": "Select Mode", + "selectRowsToDelete": "Select rows to delete", "selectPredictionMode": "Select how you want to provide input data for prediction", "targetColumn": "Target Column", "useExistingDataset": "Use Existing Dataset" @@ -70,6 +75,8 @@ "deletedSuccessfully": "Prediction deleted successfully", "downloadedSuccessfully": "Prediction downloaded successfully", "predictionCompleted": "Prediction completed successfully", - "predictionJobSubmitted": "Prediction job submitted successfully" + "predictionJobSubmitted": "Prediction job submitted successfully", + "predictionsDeleted_one": "{{count}} prediction deleted successfully", + "predictionsDeleted_other": "{{count}} predictions deleted successfully" } } diff --git a/DashAI/front/src/utils/i18n/locales/es/common.json b/DashAI/front/src/utils/i18n/locales/es/common.json index acf0f6980..78d8c4d39 100644 --- a/DashAI/front/src/utils/i18n/locales/es/common.json +++ b/DashAI/front/src/utils/i18n/locales/es/common.json @@ -13,6 +13,7 @@ "availableItems": "Elementos Disponibles", "back": "Atrás", "cancel": "Cancelar", + "clear": "Limpiar", "close": "Cerrar", "columns": "Columnas", "configureSubmodel": "Configurar submodelo", @@ -112,6 +113,8 @@ "selectTask": "Seleccionar tarea", "send": "Enviar", "sending": "Enviando...", + "runInformation": "Información del Run", + "session": "Sesión", "sessionInformation": "Información de la Sesión", "sessions": "Sesiones", "skipTour": "Saltar tutorial", @@ -141,6 +144,8 @@ "type": "Tipo", "unknown": "Desconocido", "unknownError": "Error desconocido", + "yes": "Sí", + "no": "No", "update": "Actualizar", "upload": "Subir", "user": "Usuario", @@ -160,6 +165,18 @@ "columnNameInvalidCharacters": "El nombre de la columna solo puede contener letras, números y guiones bajos", "columnNameAlreadyExists": "Ya existe una columna con este nombre", "errorRenamingColumn": "Error al renombrar la columna", + "componentDownload": { + "download": "Descargar ({{size}})", + "delete": "Eliminar descarga", + "deleteWithSize": "Eliminar descarga ({{size}})", + "downloading": "Descargando...", + "done": "Componente descargado", + "deleted": "Descarga eliminada", + "failed": "La descarga del componente ha fallado", + "mustDownload": "Este modelo debe descargarse antes de usarlo", + "mustDownloadNested": "Estos componentes deben descargarse primero: {{names}}", + "confirmDelete": "¿Eliminar los archivos descargados de {{name}}? Podrás descargarlo de nuevo más tarde." + }, "jobQueue": { "title": "Cola de trabajos", "refresh": "Actualizar", @@ -188,6 +205,7 @@ "jobType": "Tipo de trabajo", "lastUpdated": "Última actualización", "errorMessage": "Mensaje de error", + "progress": "Progreso", "unnamedJob": "Trabajo sin nombre" }, "status": { diff --git a/DashAI/front/src/utils/i18n/locales/es/custom.json b/DashAI/front/src/utils/i18n/locales/es/custom.json index c2e1699b5..ad33b4e0b 100644 --- a/DashAI/front/src/utils/i18n/locales/es/custom.json +++ b/DashAI/front/src/utils/i18n/locales/es/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Seleccione un elemento para ver la descripción.", "selectInferenceMethods": "Seleccione los métodos de inferencia que desea aplicar", "search": "Buscar", + "modelSize": "Tamaño: {{size}}", "noItemsFound": "No se encontraron componentes", "tryAdjustingSearch": "Intenta ajustar tu búsqueda o filtros", "componentsAvailable_one": "{{count}} componente disponible", diff --git a/DashAI/front/src/utils/i18n/locales/es/datasets.json b/DashAI/front/src/utils/i18n/locales/es/datasets.json index 0c23011e5..f0cf6bba2 100644 --- a/DashAI/front/src/utils/i18n/locales/es/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/es/datasets.json @@ -96,7 +96,8 @@ "requiresMinColumns_one": "Requiere al menos {{required}} columna válida, pero solo {{available}} disponible.", "requiresMinColumns_many": "Requiere al menos {{required}} columnas válidas, pero solo {{available}} disponibles.", "requiresMinColumns_other": "Requiere al menos {{required}} columnas válidas, pero solo {{available}} disponibles.", - "zipContentsNotCompatible": "El ZIP no contiene archivos compatibles con el dataloader seleccionado" + "zipContentsNotCompatible": "El ZIP no contiene archivos compatibles con el dataloader seleccionado", + "cannotSaveEmptyDataset": "No se puede guardar el dataset: se eliminaron todas las columnas." }, "label": { "task": "Tarea", @@ -260,6 +261,11 @@ "margins": "Márgenes", "marginTop": "Margen Superior", "markerColor": "Color del Marcador", + "lineColor": "Color de Línea", + "increasingColor": "Color de Aumento", + "decreasingColor": "Color de Disminución", + "totalsColor": "Color de Totales", + "transparent": "Transparente", "max": "Máximo", "maxLength": "Longitud Máxima", "mean": "Media", @@ -448,7 +454,12 @@ "showAll": "Mostrar todas", "hideAll": "Ocultar todas", "noRows": "Sin filas", + "loadingMore": "Cargando más…", + "rowsLoaded_one": "{{count}} de {{total}} fila cargada", + "rowsLoaded_many": "{{count}} de {{total}} filas cargadas", + "rowsLoaded_other": "{{count}} de {{total}} filas cargadas", "doubleClickToRename": "Doble clic para renombrar", + "predictedColumn": "Predicción", "sortedAsc": "Orden ascendente - clic para descendente", "sortedDesc": "Orden descendente - clic para quitar", "clickToSort": "Clic para ordenar ascendente", diff --git a/DashAI/front/src/utils/i18n/locales/es/explainers.json b/DashAI/front/src/utils/i18n/locales/es/explainers.json index 686a146a8..47154b54f 100644 --- a/DashAI/front/src/utils/i18n/locales/es/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/es/explainers.json @@ -3,7 +3,14 @@ "addExplainer": "Agregar Explicador", "backToExplainers": "Volver a explicadores", "hidePlot": "Ocultar Gráfico", - "showPlot": "Mostrar Gráfico" + "showPlot": "Mostrar Gráfico", + "download": "Descargar", + "downloadPng": "Descargar como PNG", + "downloadSvg": "Descargar como SVG", + "editPlot": "Editar gráfico", + "resetPlot": "Restablecer ediciones", + "fullscreen": "Pantalla completa", + "close": "Cerrar" }, "error": { "explainerFailed": "Error al generar el explicador.", @@ -54,9 +61,23 @@ "selectDatasetToExplain": "Seleccione un dataset con instancias para explicar", "selectExplainer": "Establecer nombre y explicador", "selectExplainerAndName": "Seleccione un explicador e ingrese un nombre", + "selectInstance": "Selecciona una instancia", "explainerInProgress": "Explicador en progreso...", "splitSelectionSummary": "Porcentaje: {{percentage}}% | Filas seleccionadas: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Buscar explicadores..." + "searchExplainers": "Buscar explicadores...", + "sourceFromDataset": "Desde un dataset", + "sourceManualInput": "Ingresar manualmente", + "enterInstancesManually": "Ingrese las instancias a explicar", + "manualInputDescription": "Complete los valores de las características para cada instancia que desea explicar.", + "explanationInfo": "Información de la Explicación", + "inputColumns": "Columnas de Entrada:", + "targetColumn": "Columna Objetivo:", + "markRowsToExplain": "Marque las filas a explicar", + "rowSelectionMode": "Modo de selección de filas", + "rowModePercentage": "Control deslizante de porcentaje", + "rowModeManual": "Selección manual", + "shuffleRows": "Mezclar filas seleccionadas (muestra aleatoria)", + "rowsSelectedManually": "Filas seleccionadas manualmente: {{selected}} / {{total}}" }, "message": { "explainerJobCompleted": "Explicador {{name}} completado exitosamente", diff --git a/DashAI/front/src/utils/i18n/locales/es/generative.json b/DashAI/front/src/utils/i18n/locales/es/generative.json index 97f059735..fcf27f01a 100644 --- a/DashAI/front/src/utils/i18n/locales/es/generative.json +++ b/DashAI/front/src/utils/i18n/locales/es/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Se requiere un nombre", "processError": "El proceso ha fallado. Eliminándolo... {{error}}", "sessionNameEmpty": "El nombre de la sesión no puede estar vacío", - "sessionNameExists": "Ya existe una sesión con este nombre" + "sessionNameExists": "Ya existe una sesión con este nombre", + "modelSwitchFailed": "No se pudo cambiar el modelo de la sesión" }, "label": { "confirmDeleteSession": "¿Seguro que quieres eliminar la sesión \"{{name}}\"? Esta acción no se puede deshacer.", @@ -38,6 +39,8 @@ "startBySelectingATask": "Comienza seleccionando una tarea generativa.", "noSessionsFound": "No se encontraron sesiones", "parameterChangeEvent": "Parámetros actualizados: <1>", + "modelChangeEvent": "Modelo cambiado: <1>", + "modelNotDownloaded": "El modelo de esta sesión aún no está descargado. Descárgalo para continuar.", "parameterChangeHistory": "Historial de cambios de parámetros para la sesión actual", "searchSessions": "Buscar Sesiones", "selectGenerativeTask": "Seleccione una tarea generativa para comenzar una nueva sesión", @@ -57,7 +60,9 @@ "attachMedia": "Adjuntar medio", "attachMediaToContinue": "Adjunta medios para continuar", "noInputAvailable": "No hay entrada disponible para esta tarea", - "selectModelCrumb": "Select Model" + "selectModelCrumb": "Select Model", + "sessionModel": "Modelo", + "downloadRequired": "descarga requerida" }, "message": { "sessionCreatedSuccess": "Sesión creada exitosamente.", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index 7ede341d8..b0a2b8038 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -5,6 +5,7 @@ "createLocalExplainer": "Nuevo Explicador Local", "createPrediction": "Crear Predicción", "createSession": "Crear Sesión", + "addNewPrediction": "Agregar Nueva Predicción", "deleteAndRetrain": "Eliminar y Re-entrenar", "deleteRun": "Eliminar Ejecución", "hideOperations": "Ocultar Operaciones", @@ -61,14 +62,23 @@ "label": { "addModelToSession": "Agregar Modelo a la Sesión", "confirmDeleteSession": "¿Seguro que quieres eliminar la sesión \"{{name}}\"? Esta acción no se puede deshacer.", + "availableExplainers": "Explicadores Disponibles", "availableModels": "Modelos Disponibles", + "modelCount_one": "{{count}} modelo", + "modelCount_many": "{{count}} modelos", + "modelCount_other": "{{count}} modelos", "bar": "Barra", "heatmap": "Mapa de calor", "chooseTaskForSessionWithDataset": "Elija la tarea de aprendizaje automático para su sesión con el dataset \"{{datasetName}}\".", + "configuration": "Configuración", + "modelConfiguration": "Configuración del Modelo", + "sessionConfiguration": "Configuración de la Sesión", "configureModel": "Configurar Modelo", "configureOptimizer": "Configurar Optimizador", "configureSession": "Configurar Sesión", "configureTasksTrainCompareModels": "Configure tareas, entrene y compare modelos en sesiones organizadas. Seleccione una tarea para comenzar su flujo de trabajo de modelado.", + "inputColumns": "Columnas de entrada", + "outputColumns": "Columnas de salida", "customMetrics": "Métricas Personalizadas", "datasetPredictions": "Predicciones de Dataset", "editRunParameters": "Editar parámetros y volver a ejecutar el modelo", @@ -92,25 +102,32 @@ "localExplainer": "Explicador Local", "localExplainers": "Explicadores Locales", "manualPredictions": "Predicciones Manuales", + "manual": "Manuales", "metricsEmptyForDisplaySet": "Las métricas de resultado para {{set}} están vacías.", "metrics": "Métricas", "metricToOptimize": "Métrica a Optimizar", + "searchMetric": "Buscar métrica...", "modelComparison": "Comparación de Modelos", "modelsModule": "Módulo de Modelos", "nameYourSession": "Nombre su Sesión", "selectDatasetAndPrepare": "Nombra tu sesión, selecciona un dataset y configura sus columnas y particiones.", + "noCompatibleExplainersFound": "No se encontraron explicadores compatibles", "noCompatibleModelsFound": "No se encontraron modelos compatibles", + "noConfigurationAvailable": "No hay configuración disponible para esta ejecución", "dropModelHere": "Suelta aquí para agregar", + "noCompletedRuns": "Aún no hay ejecuciones completadas", "noDatasetPredictionsYet": "Aún no hay predicciones de dataset", "noGlobalExplainersYet": "Aún no hay explicadores globales", "noLocalExplainersYet": "Aún no hay explicadores locales", "noManualPredictionsYet": "Aún no hay predicciones manuales", "noMetricsAvailable": "No hay métricas disponibles", "noMetricsAvailableForThisView": "No hay métricas disponibles para esta vista", + "noExplainersMatchSearch": "No hay explicadores que coincidan con su búsqueda", "noModelsMatchSearch": "No hay modelos que coincidan con su búsqueda", "noPredictionsYet": "Aún no hay predicciones", "noRunsYet": "Aún no hay ejecuciones. Agregue modelos desde el panel derecho.", "noSessionSelected": "No se Seleccionó Ninguna Sesión", + "operations": "Operaciones", "operationsWillBeDeletedWarning": "Estas operaciones se eliminarán permanentemente y no se pueden recuperar. ¿Está seguro de que desea continuar?", "optimizerConfiguration": "Configure los ajustes del optimizador de hiperparámetros", "optimizerParameters": "Parámetros del Optimizador", @@ -127,9 +144,11 @@ "retrainWillDeleteOperations": "Esta ejecución tiene operaciones existentes que serán eliminadas", "retrainWillDeleteOperationsDetails": "Re-entrenar la ejecución \"<1>{{runName}}\" eliminará:", "saveParameterChanges": "¿Guardar Cambios de Parámetros?", + "saveConfirmDetails": "Guardar \"<1>{{runName}}\" reiniciará su estado a 'No Iniciado' y borrará sus métricas y resultados actuales. ¿Está seguro de que desea continuar?", "showResults": "Mostrar Resultados", "saveWillDeleteOperationsDetails": "Guardar \"<1>{{runName}}\" restablecerá la ejecución. Lo siguiente se eliminará cuando vuelva a entrenar:", "runDetails": "Detalles de la Ejecución", + "runNotFound": "Ejecución no encontrada", "runExperimentToSeeMetrics": "Ve a la <1>pestaña de experimentos para ejecutar tu experimento.", "runFailedNoHyperparameterPlots": "Ejecución Fallida. No hay gráficos de hiperparámetros disponibles.", "runInProgressCannotEdit": "La ejecución está actualmente en progreso y no puede ser editada.", @@ -139,6 +158,7 @@ "searchModels": "Buscar Modelos...", "selectDataset": "Seleccionar Dataset", "selectDatasetForSession": "Seleccione un dataset para su sesión", + "exitModelDetailToAddModels": "Vuelve a la vista general de la sesión para agregar más modelos.", "selectSessionToViewModels": "Seleccione una sesión para ver los modelos disponibles.", "selectTask": "Seleccionar una Tarea", "selectTaskForSession": "Seleccione una Tarea para Su Sesión", @@ -160,26 +180,14 @@ "higherIsBetter": "Mayor es mejor", "lowerIsBetter": "Menor es mejor", "bestModel": "Mejor", - "score": "Puntuación", - "scoreProfile": "Perfil de Puntuación", - "scoreHeaderTooltip": "Puntuación ponderada 0–100 según el perfil seleccionado. Las métricas de error sin límite superior (MAE, RMSE, TER, …) se normalizan respecto al peor modelo en esta comparación. Pasa el cursor sobre una celda para ver el desglose.", - "profile_balanced": "Equilibrio", - "profile_detectPositives": "Detectar Positivos", - "profile_avoidFalseAlarms": "Evitar Falsas Alarmas", - "profile_probabilityQuality": "Calidad de Probabilidades", - "profile_regression_fit": "Ajuste del Modelo", - "profile_regression_error": "Error Balanceado", - "profile_translation_quality": "Calidad de Traducción", - "profile_translation_balanced": "Traducción Balanceada", - "profile_text_balanced": "Equilibrio", - "profile_text_detectPositives": "Detectar Positivos", - "profile_text_avoidFalseAlarms": "Evitar Falsas Alarmas", - "profile_text_probabilityQuality": "Calidad de Probabilidades", "editRun": "Editar ejecución", "chartType": "Tipo de gráfico" }, "message": { + "skippedUndownloadedRuns": "Se omitieron las ejecuciones cuyo modelo no está descargado. Descarga el modelo primero.", "allRunsCompleted": "{{experiment}} ha completado todas sus ejecuciones.", + "tabAvailableAfterFinish": "Estará disponible cuando el run termine de entrenar.", + "noOptimizableParamsForHpo": "Ningún parámetro está marcado para optimización — edita la configuración del run para habilitar esto.", "confirmDeleteRun": "¿Está seguro de que desea eliminar esta ejecución? Esta acción no se puede deshacer.", "editingParametersWarning": "Al editar los parámetros se restablecerá el estado de esta ejecución a 'No Iniciado'. Todas las métricas y resultados existentes se perderán.", "aboutToUpdateParameters": "Está a punto de actualizar los parámetros para la ejecución {{runName}}. Esta acción tendrá las siguientes consecuencias:", diff --git a/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json index 291d8e047..6c3953460 100644 --- a/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Tarjeta de Ejecución del Modelo<1>¡Perfecto! Esta tarjeta contiene todo sobre la ejecución de tu modelo. Aquí puedes:<2><0><0>Entrenar: Iniciar el proceso de entrenamiento con tus parámetros configurados<1><0>Ver Métricas: Ver las puntuaciones de rendimiento una vez completado el entrenamiento<2><0>Hacer Predicciones: Usar tu modelo entrenado con datos nuevos<3><0>Crear Explicadores: Entender cómo tu modelo toma decisiones<3>¡Entrenemos este modelo para ver cómo funciona!", "performanceVisualizations": "<0><0>Visualizaciones de Rendimiento<1>La vista de gráficos muestra métricas de rendimiento y otras visualizaciones para ayudarte a entender mejor el rendimiento de tus modelos.<2>🎉 <0>¡Excelente trabajo! ¡Ahora puedes agregar más modelos y experimentar con diferentes parámetros!", "sessionVisualization": "<0><0>Visualización de Sesión<1>¡Bienvenido(a) a la Visualización de Sesión! Aquí es donde puedes comparar diferentes modelos, entrenarlos y analizar su rendimiento.<2>Comencemos agregando algunos modelos para comparar en esta sesión.", - "trainModel": "<0><0>Entrena tu Modelo<1>Haz clic en el botón <0>Entrenar para comenzar a entrenar tu modelo con los parámetros configurados.<2>El proceso de entrenamiento se ejecutará en segundo plano, y podrás ver el progreso y los resultados aquí.<3><0>¡Haz clic en \"Entrenar\" para continuar!", - "visualizeResults": "<0><0>Visualizar Resultados<1>¿Quieres ver tus resultados de una manera más visual? Haz clic en el botón <0>Gráficos para cambiar de la vista de tabla a gráficos interactivos.<2><0>¡Haz clic en \"Gráficos\" para continuar!" + "trainModel": "<0><0>Entrena tu Modelo<1>Haz clic en el botón <0>Entrenar para comenzar a entrenar tu modelo con los parámetros configurados.<2>El proceso de entrenamiento se ejecutará en segundo plano, y podrás ver el progreso y los resultados aquí.<3><0>¡Haz clic en \"Entrenar\" para continuar!" } diff --git a/DashAI/front/src/utils/i18n/locales/es/prediction.json b/DashAI/front/src/utils/i18n/locales/es/prediction.json index d14c4ccb5..2760ddda2 100644 --- a/DashAI/front/src/utils/i18n/locales/es/prediction.json +++ b/DashAI/front/src/utils/i18n/locales/es/prediction.json @@ -1,5 +1,8 @@ { "button": { + "deleteSelected_one": "Eliminar Seleccionada ({{count}})", + "deleteSelected_many": "Eliminar Seleccionadas ({{count}})", + "deleteSelected_other": "Eliminar Seleccionadas ({{count}})", "downloadResults": "Descargar Resultados de Predicción", "hideResults": "Ocultar Resultados", "runPrediction": "Ejecutar Predicción", @@ -9,6 +12,7 @@ "error": { "creatingPrediction": "Error al crear predicción", "errorDeleting": "Error al eliminar predicción", + "errorDeletingSelected": "Error al eliminar las predicciones seleccionadas", "errorDownloading": "Error al descargar predicción", "fetchingDatasets": "Error al obtener datasets para predicción.", "loadingPredictionData": "Error al cargar datos de predicción", @@ -25,6 +29,9 @@ "clickToViewOrDelete": "Haga clic en una predicción para ver los detalles o eliminarla usando el icono de eliminar.", "configureInput": "Configurar Entrada", "confirm": "Confirmar", + "confirmBulkDeletion_one": "¿Está seguro de que desea eliminar la predicción seleccionada? Esta acción no se puede deshacer.", + "confirmBulkDeletion_many": "¿Está seguro de que desea eliminar las {{count}} predicciones seleccionadas? Esta acción no se puede deshacer.", + "confirmBulkDeletion_other": "¿Está seguro de que desea eliminar las {{count}} predicciones seleccionadas? Esta acción no se puede deshacer.", "confirmDeletion": "¿Está seguro de que desea eliminar esta predicción? Esta acción no se puede deshacer.", "confirmDeletionTitle": "¿Eliminar Predicción?", "confirmPrediction": "Confirmar Predicción", @@ -52,7 +59,6 @@ "previewResults": "Vista Previa de Resultados", "previousPredictions": "Predicciones Anteriores", "provideManualInput": "Ingrese datos manualmente para la predicción. Complete los campos a continuación y envíe cuando esté listo.", - "resultsPreview": "Vista previa de las primeras 100 filas. Descargue el CSV para obtener resultados completos.", "resultsPreviewDownloadInfo": "La tabla a continuación muestra una vista previa de los resultados de la predicción. Puede descargar los resultados completos como un archivo CSV usando el botón \"Descargar CSV\" a continuación.", "reviewDetails": "Revise la configuración de su predicción antes de enviar", "runFirstPrediction": "Ejecute su primera predicción para verla aquí", @@ -62,6 +68,7 @@ "selectDataset": "Seleccione un dataset para la predicción", "selectDatasetFromPlatform": "Seleccione un dataset de la plataforma", "selectMode": "Seleccionar Modo", + "selectRowsToDelete": "Seleccionar filas para eliminar", "selectPredictionMode": "Seleccione cómo desea proporcionar datos de entrada para la predicción", "targetColumn": "Columna Objetivo", "useExistingDataset": "Usar Dataset Existente" @@ -70,6 +77,9 @@ "deletedSuccessfully": "Predicción eliminada exitosamente", "downloadedSuccessfully": "Predicción descargada exitosamente", "predictionCompleted": "Predicción completada exitosamente", - "predictionJobSubmitted": "Trabajo de predicción enviado exitosamente" + "predictionJobSubmitted": "Trabajo de predicción enviado exitosamente", + "predictionsDeleted_one": "{{count}} predicción eliminada correctamente", + "predictionsDeleted_many": "{{count}} predicciones eliminadas correctamente", + "predictionsDeleted_other": "{{count}} predicciones eliminadas correctamente" } } diff --git a/DashAI/front/src/utils/i18n/locales/pt/common.json b/DashAI/front/src/utils/i18n/locales/pt/common.json index 201094865..2dbb2c145 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/common.json +++ b/DashAI/front/src/utils/i18n/locales/pt/common.json @@ -13,6 +13,7 @@ "availableItems": "Itens Disponíveis", "back": "Voltar", "cancel": "Cancelar", + "clear": "Limpar", "close": "Fechar", "columns": "Colunas", "configureSubmodel": "Configurar submodelo", @@ -112,6 +113,8 @@ "selectTask": "Selecionar tarefa", "send": "Enviar", "sending": "Enviando...", + "runInformation": "Informações da Execução", + "session": "Sessão", "sessionInformation": "Informações da Sessão", "sessions": "Sessões", "skipTour": "Pular tutorial", @@ -141,6 +144,8 @@ "type": "Tipo", "unknown": "Desconhecido", "unknownError": "Erro desconhecido", + "yes": "Sim", + "no": "Não", "update": "Atualizar", "upload": "Enviar", "user": "Usuário", @@ -160,6 +165,18 @@ "columnNameInvalidCharacters": "O nome da coluna só pode conter letras, números e underscores", "columnNameAlreadyExists": "Já existe uma coluna com este nome", "errorRenamingColumn": "Erro ao renomear a coluna", + "componentDownload": { + "download": "Baixar ({{size}})", + "delete": "Remover download", + "deleteWithSize": "Remover download ({{size}})", + "downloading": "Baixando...", + "done": "Componente baixado", + "deleted": "Download removido", + "failed": "Falha ao baixar o componente", + "mustDownload": "Este modelo precisa ser baixado antes de usar", + "mustDownloadNested": "Estes componentes precisam ser baixados primeiro: {{names}}", + "confirmDelete": "Excluir os arquivos baixados de {{name}}? Você poderá baixá-lo novamente mais tarde." + }, "jobQueue": { "title": "Fila de trabalhos", "refresh": "Atualizar", @@ -188,6 +205,7 @@ "jobType": "Tipo de trabalho", "lastUpdated": "Última atualização", "errorMessage": "Mensagem de erro", + "progress": "Progresso", "unnamedJob": "Trabalho sem nome" }, "status": { diff --git a/DashAI/front/src/utils/i18n/locales/pt/custom.json b/DashAI/front/src/utils/i18n/locales/pt/custom.json index 38a1242a2..054d5b567 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/custom.json +++ b/DashAI/front/src/utils/i18n/locales/pt/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Selecione um elemento para ver a descrição.", "selectInferenceMethods": "Selecione os métodos de inferência que deseja aplicar", "search": "Buscar", + "modelSize": "Tamanho: {{size}}", "noItemsFound": "Nenhum componente encontrado", "tryAdjustingSearch": "Tente ajustar sua busca ou filtros", "componentsAvailable_one": "{{count}} componente disponível", diff --git a/DashAI/front/src/utils/i18n/locales/pt/datasets.json b/DashAI/front/src/utils/i18n/locales/pt/datasets.json index d2230eeb5..32a738176 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/pt/datasets.json @@ -96,7 +96,8 @@ "requiresMinColumns_one": "Requer pelo menos {{required}} coluna válida, mas apenas {{available}} disponível.", "requiresMinColumns_many": "Requer pelo menos {{required}} colunas válidas, mas apenas {{available}} disponíveis.", "requiresMinColumns_other": "Requer pelo menos {{required}} colunas válidas, mas apenas {{available}} disponíveis.", - "zipContentsNotCompatible": "O arquivo ZIP não contém arquivos compatíveis com o dataloader selecionado" + "zipContentsNotCompatible": "O arquivo ZIP não contém arquivos compatíveis com o dataloader selecionado", + "cannotSaveEmptyDataset": "Não é possível salvar o dataset: todas as colunas foram removidas." }, "label": { "task": "Tarefa", @@ -260,6 +261,11 @@ "margins": "Margens", "marginTop": "Margem Superior", "markerColor": "Cor do Marcador", + "lineColor": "Cor da Linha", + "increasingColor": "Cor de Aumento", + "decreasingColor": "Cor de Diminuição", + "totalsColor": "Cor dos Totais", + "transparent": "Transparente", "max": "Máximo", "maxLength": "Comprimento Máximo", "mean": "Média", @@ -448,7 +454,12 @@ "showAll": "Mostrar todas", "hideAll": "Ocultar todas", "noRows": "Sem linhas", + "loadingMore": "Carregando mais…", + "rowsLoaded_one": "{{count}} de {{total}} linha carregada", + "rowsLoaded_many": "{{count}} de {{total}} linhas carregadas", + "rowsLoaded_other": "{{count}} de {{total}} linhas carregadas", "doubleClickToRename": "Clique duplo para renomear", + "predictedColumn": "Previsão", "sortedAsc": "Ordem crescente - clique para decrescente", "sortedDesc": "Ordem decrescente - clique para remover", "clickToSort": "Clique para ordenar crescente", diff --git a/DashAI/front/src/utils/i18n/locales/pt/explainers.json b/DashAI/front/src/utils/i18n/locales/pt/explainers.json index e2e0f82fa..9c8a1c44c 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/pt/explainers.json @@ -3,7 +3,14 @@ "addExplainer": "Adicionar Explicador", "backToExplainers": "Voltar para explicadores", "hidePlot": "Ocultar Gráfico", - "showPlot": "Mostrar Gráfico" + "showPlot": "Mostrar Gráfico", + "download": "Baixar", + "downloadPng": "Baixar como PNG", + "downloadSvg": "Baixar como SVG", + "editPlot": "Editar gráfico", + "resetPlot": "Redefinir edições", + "fullscreen": "Tela cheia", + "close": "Fechar" }, "error": { "explainerFailed": "Erro ao gerar o explicador.", @@ -54,9 +61,23 @@ "selectDatasetToExplain": "Selecione um conjunto de dados com instâncias para explicar", "selectExplainer": "Definir nome e explicador", "selectExplainerAndName": "Selecione um explicador e insira um nome", + "selectInstance": "Selecione uma instância", "explainerInProgress": "Explicador em andamento...", "splitSelectionSummary": "Percentual: {{percentage}}% | Linhas selecionadas: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Pesquisar explicadores..." + "searchExplainers": "Pesquisar explicadores...", + "sourceFromDataset": "De um conjunto de dados", + "sourceManualInput": "Inserir manualmente", + "enterInstancesManually": "Insira as instâncias a explicar", + "manualInputDescription": "Preencha os valores das características para cada instância que deseja explicar.", + "explanationInfo": "Informações da Explicação", + "inputColumns": "Colunas de Entrada:", + "targetColumn": "Coluna Alvo:", + "markRowsToExplain": "Marque as linhas a explicar", + "rowSelectionMode": "Modo de seleção de linhas", + "rowModePercentage": "Controle deslizante de porcentagem", + "rowModeManual": "Seleção manual", + "shuffleRows": "Embaralhar linhas selecionadas (amostra aleatória)", + "rowsSelectedManually": "Linhas selecionadas manualmente: {{selected}} / {{total}}" }, "message": { "explainerJobCompleted": "Explicador {{name}} concluído com sucesso", diff --git a/DashAI/front/src/utils/i18n/locales/pt/generative.json b/DashAI/front/src/utils/i18n/locales/pt/generative.json index c2b513a51..06643287f 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/generative.json +++ b/DashAI/front/src/utils/i18n/locales/pt/generative.json @@ -17,7 +17,8 @@ "nameRequired": "É necessário um nome", "processError": "O processo falhou. Excluindo... {{error}}", "sessionNameEmpty": "O nome da sessão não pode estar vazio", - "sessionNameExists": "Já existe uma sessão com este nome" + "sessionNameExists": "Já existe uma sessão com este nome", + "modelSwitchFailed": "Falha ao alterar o modelo da sessão" }, "label": { "confirmDeleteSession": "Tem certeza de que deseja excluir a sessão \"{{name}}\"? Esta ação não pode ser desfeita.", @@ -38,6 +39,8 @@ "startBySelectingATask": "Comece selecionando uma tarefa generativa.", "noSessionsFound": "Nenhuma sessão encontrada", "parameterChangeEvent": "Parâmetros atualizados: <1>", + "modelChangeEvent": "Modelo alterado: <1>", + "modelNotDownloaded": "O modelo desta sessão ainda não foi baixado. Baixe-o para continuar.", "parameterChangeHistory": "Histórico de alterações de parâmetros para a sessão atual", "searchSessions": "Buscar Sessões", "selectGenerativeTask": "Selecione uma tarefa generativa para começar uma nova sessão", @@ -57,7 +60,9 @@ "attachMedia": "Anexar mídia", "attachMediaToContinue": "Anexe uma mídia para continuar", "noInputAvailable": "Não há entrada disponível para esta tarefa", - "selectModelCrumb": "Selecionar Modelo" + "selectModelCrumb": "Selecionar Modelo", + "sessionModel": "Modelo", + "downloadRequired": "download necessário" }, "message": { "sessionCreatedSuccess": "Sessão criada com sucesso.", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index 50f551a17..0080961dc 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -5,6 +5,7 @@ "createLocalExplainer": "Novo Explicador Local", "createPrediction": "Criar Previsão", "createSession": "Criar Sessão", + "addNewPrediction": "Adicionar Nova Previsão", "deleteAndRetrain": "Excluir e Retreinar", "deleteRun": "Excluir Execução", "hideOperations": "Ocultar Operações", @@ -61,14 +62,23 @@ "label": { "addModelToSession": "Adicionar Modelo à Sessão", "confirmDeleteSession": "Tem certeza de que deseja excluir a sessão \"{{name}}\"? Esta ação não pode ser desfeita.", + "availableExplainers": "Explicadores Disponíveis", "availableModels": "Modelos Disponíveis", + "modelCount_one": "{{count}} modelo", + "modelCount_many": "{{count}} modelos", + "modelCount_other": "{{count}} modelos", "bar": "Barra", "heatmap": "Mapa de calor", "chooseTaskForSessionWithDataset": "Escolha a tarefa de aprendizado de máquina para sua sessão com o conjunto de dados \"{{datasetName}}\".", + "configuration": "Configuração", + "modelConfiguration": "Configuração do Modelo", + "sessionConfiguration": "Configuração da Sessão", "configureModel": "Configurar Modelo", "configureOptimizer": "Configurar Otimizador", "configureSession": "Configurar Sessão", "configureTasksTrainCompareModels": "Configure tarefas, treine e compare modelos em sessões organizadas. Selecione uma tarefa para começar seu fluxo de trabalho de modelagem.", + "inputColumns": "Colunas de entrada", + "outputColumns": "Colunas de saída", "customMetrics": "Métricas Personalizadas", "datasetPredictions": "Previsões de Conjunto de Dados", "editRunParameters": "Editar parâmetros e executar novamente o modelo", @@ -92,25 +102,32 @@ "localExplainer": "Explicador Local", "localExplainers": "Explicadores Locais", "manualPredictions": "Previsões Manuais", + "manual": "Manuais", "metricsEmptyForDisplaySet": "As métricas de resultado para {{set}} estão vazias.", "metrics": "Métricas", "metricToOptimize": "Métrica a Otimizar", + "searchMetric": "Buscar métrica...", "modelComparison": "Comparação de Modelos", "modelsModule": "Módulo de Modelos", "nameYourSession": "Nomeie sua Sessão", "selectDatasetAndPrepare": "Nomeie sua sessão, selecione um conjunto de dados e configure suas colunas e partições.", + "noCompatibleExplainersFound": "Nenhum explicador compatível encontrado", "noCompatibleModelsFound": "Nenhum modelo compatível encontrado", + "noConfigurationAvailable": "Nenhuma configuração disponível para esta execução", "dropModelHere": "Solte aqui para adicionar", + "noCompletedRuns": "Ainda não há execuções concluídas", "noDatasetPredictionsYet": "Ainda não há previsões de conjunto de dados", "noGlobalExplainersYet": "Ainda não há explicadores globais", "noLocalExplainersYet": "Ainda não há explicadores locais", "noManualPredictionsYet": "Ainda não há previsões manuais", "noMetricsAvailable": "Não há métricas disponíveis", "noMetricsAvailableForThisView": "Não há métricas disponíveis para esta visualização", + "noExplainersMatchSearch": "Nenhum explicador corresponde à sua busca", "noModelsMatchSearch": "Nenhum modelo corresponde à sua busca", "noPredictionsYet": "Ainda não há previsões", "noRunsYet": "Ainda não há execuções. Adicione modelos pelo painel direito.", "noSessionSelected": "Nenhuma Sessão Selecionada", + "operations": "Operações", "operationsWillBeDeletedWarning": "Estas operações serão excluídas permanentemente e não podem ser recuperadas. Tem certeza de que deseja continuar?", "optimizerConfiguration": "Configure as definições do otimizador de hiperparâmetros", "optimizerParameters": "Parâmetros do Otimizador", @@ -127,9 +144,11 @@ "retrainWillDeleteOperations": "Esta execução possui operações existentes que serão excluídas", "retrainWillDeleteOperationsDetails": "Retreinar a execução \"<1>{{runName}}\" excluirá:", "saveParameterChanges": "Salvar Alterações de Parâmetros?", + "saveConfirmDetails": "Salvar \"<1>{{runName}}\" redefinirá seu status para 'Não Iniciado' e apagará suas métricas e resultados atuais. Tem certeza de que deseja continuar?", "showResults": "Mostrar Resultados", "saveWillDeleteOperationsDetails": "Salvar \"<1>{{runName}}\" redefinirá a execução. O seguinte será excluído ao retreinar:", "runDetails": "Detalhes da Execução", + "runNotFound": "Execução não encontrada", "runExperimentToSeeMetrics": "Vá para a <1>aba de experimentos para executar seu experimento.", "runFailedNoHyperparameterPlots": "Execução Falhou. Não há gráficos de hiperparâmetros disponíveis.", "runInProgressCannotEdit": "A execução está em andamento e não pode ser editada.", @@ -139,6 +158,7 @@ "searchModels": "Buscar Modelos...", "selectDataset": "Selecionar Conjunto de Dados", "selectDatasetForSession": "Selecione um conjunto de dados para sua sessão", + "exitModelDetailToAddModels": "Volte para a visão geral da sessão para adicionar mais modelos.", "selectSessionToViewModels": "Selecione uma sessão para ver os modelos disponíveis.", "selectTask": "Selecionar uma Tarefa", "selectTaskForSession": "Selecione uma Tarefa para sua Sessão", @@ -160,26 +180,14 @@ "higherIsBetter": "Maior é melhor", "lowerIsBetter": "Menor é melhor", "bestModel": "Melhor", - "score": "Pontuação", - "scoreProfile": "Perfil de Pontuação", - "scoreHeaderTooltip": "Pontuação ponderada 0–100 conforme o perfil selecionado. As métricas de erro sem limite superior (MAE, RMSE, TER, …) são normalizadas em relação ao pior modelo nesta comparação. Passe o cursor sobre uma célula para ver o detalhamento.", - "profile_balanced": "Equilíbrio", - "profile_detectPositives": "Detectar Positivos", - "profile_avoidFalseAlarms": "Evitar Falsos Alarmes", - "profile_probabilityQuality": "Qualidade de Probabilidades", - "profile_regression_fit": "Ajuste do Modelo", - "profile_regression_error": "Erro Balanceado", - "profile_translation_quality": "Qualidade de Tradução", - "profile_translation_balanced": "Tradução Balanceada", - "profile_text_balanced": "Equilíbrio", - "profile_text_detectPositives": "Detectar Positivos", - "profile_text_avoidFalseAlarms": "Evitar Falsos Alarmes", - "profile_text_probabilityQuality": "Qualidade de Probabilidades", "editRun": "Editar execução", "chartType": "Tipo de gráfico" }, "message": { + "skippedUndownloadedRuns": "Execuções cujo modelo não está baixado foram ignoradas. Baixe o modelo primeiro.", "allRunsCompleted": "{{experiment}} concluiu todas as suas execuções.", + "tabAvailableAfterFinish": "Estará disponível quando a execução terminar o treinamento.", + "noOptimizableParamsForHpo": "Nenhum parâmetro está marcado para otimização — edite a configuração da execução para habilitar isso.", "confirmDeleteRun": "Tem certeza de que deseja excluir esta execução? Esta ação não pode ser desfeita.", "editingParametersWarning": "Ao editar os parâmetros, o estado desta execução será redefinido para 'Não Iniciado'. Todas as métricas e resultados existentes serão perdidos.", "aboutToUpdateParameters": "Você está prestes a atualizar os parâmetros para a execução {{runName}}. Esta ação terá as seguintes consequências:", diff --git a/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json index 9f31219fc..e4721c9cf 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Cartão de Execução do Modelo<1>Perfeito! Este cartão contém tudo sobre a execução do seu modelo. Aqui você pode:<2><0><0>Treinar: Iniciar o processo de treinamento com seus parâmetros configurados<1><0>Ver Métricas: Ver as pontuações de desempenho após concluir o treinamento<2><0>Fazer Previsões: Usar seu modelo treinado com novos dados<3><0>Criar Explicadores: Entender como seu modelo toma decisões<3>Vamos treinar este modelo para ver como funciona!", "performanceVisualizations": "<0><0>Visualizações de Desempenho<1>A visualização de gráficos mostra métricas de desempenho e outras visualizações para ajudá-lo(a) a entender melhor o desempenho dos seus modelos.<2>🎉 <0>Excelente trabalho! Agora você pode adicionar mais modelos e experimentar com diferentes parâmetros!", "sessionVisualization": "<0><0>Visualização de Sessão<1>Bem-vindo(a) à Visualização de Sessão! Aqui é onde você pode comparar diferentes modelos, treiná-los e analisar seu desempenho.<2>Vamos começar adicionando alguns modelos para comparar nesta sessão.", - "trainModel": "<0><0>Treine seu Modelo<1>Clique no botão <0>Treinar para começar a treinar seu modelo com os parâmetros configurados.<2>O processo de treinamento será executado em segundo plano e você poderá ver o progresso e os resultados aqui.<3><0>Clique em \"Treinar\" para continuar!", - "visualizeResults": "<0><0>Visualizar Resultados<1>Quer ver seus resultados de forma mais visual? Clique no botão <0>Gráficos para alternar da visualização de tabela para gráficos interativos.<2><0>Clique em \"Gráficos\" para continuar!" + "trainModel": "<0><0>Treine seu Modelo<1>Clique no botão <0>Treinar para começar a treinar seu modelo com os parâmetros configurados.<2>O processo de treinamento será executado em segundo plano e você poderá ver o progresso e os resultados aqui.<3><0>Clique em \"Treinar\" para continuar!" } diff --git a/DashAI/front/src/utils/i18n/locales/pt/prediction.json b/DashAI/front/src/utils/i18n/locales/pt/prediction.json index 26498ca10..a2ee1d879 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/prediction.json +++ b/DashAI/front/src/utils/i18n/locales/pt/prediction.json @@ -1,5 +1,8 @@ { "button": { + "deleteSelected_one": "Excluir Selecionada ({{count}})", + "deleteSelected_many": "Excluir Selecionadas ({{count}})", + "deleteSelected_other": "Excluir Selecionadas ({{count}})", "downloadResults": "Baixar Resultados da Previsão", "hideResults": "Ocultar Resultados", "runPrediction": "Executar Previsão", @@ -9,6 +12,7 @@ "error": { "creatingPrediction": "Erro ao criar previsão", "errorDeleting": "Erro ao excluir previsão", + "errorDeletingSelected": "Erro ao excluir as previsões selecionadas", "errorDownloading": "Erro ao baixar previsão", "fetchingDatasets": "Erro ao obter conjuntos de dados para previsão.", "loadingPredictionData": "Erro ao carregar dados de previsão", @@ -25,6 +29,9 @@ "clickToViewOrDelete": "Clique em uma previsão para ver os detalhes ou excluí-la usando o ícone de excluir.", "configureInput": "Configurar Entrada", "confirm": "Confirmar", + "confirmBulkDeletion_one": "Tem certeza de que deseja excluir a previsão selecionada? Esta ação não pode ser desfeita.", + "confirmBulkDeletion_many": "Tem certeza de que deseja excluir as {{count}} previsões selecionadas? Esta ação não pode ser desfeita.", + "confirmBulkDeletion_other": "Tem certeza de que deseja excluir as {{count}} previsões selecionadas? Esta ação não pode ser desfeita.", "confirmDeletion": "Tem certeza de que deseja excluir esta previsão? Esta ação não pode ser desfeita.", "confirmDeletionTitle": "Excluir Previsão?", "confirmPrediction": "Confirmar Previsão", @@ -52,7 +59,6 @@ "previewResults": "Visualização Prévia de Resultados", "previousPredictions": "Previsões Anteriores", "provideManualInput": "Insira dados manualmente para a previsão. Preencha os campos abaixo e envie quando estiver pronto.", - "resultsPreview": "Visualização prévia das primeiras 100 linhas. Baixe o CSV para obter resultados completos.", "resultsPreviewDownloadInfo": "A tabela abaixo mostra uma visualização prévia dos resultados da previsão. Você pode baixar os resultados completos como um arquivo CSV usando o botão \"Baixar CSV\" abaixo.", "reviewDetails": "Revise a configuração da sua previsão antes de enviar", "runFirstPrediction": "Execute sua primeira previsão para vê-la aqui", @@ -62,6 +68,7 @@ "selectDataset": "Selecione um conjunto de dados para a previsão", "selectDatasetFromPlatform": "Selecione um conjunto de dados da plataforma", "selectMode": "Selecionar Modo", + "selectRowsToDelete": "Selecionar linhas para excluir", "selectPredictionMode": "Selecione como deseja fornecer dados de entrada para a previsão", "targetColumn": "Coluna Alvo", "useExistingDataset": "Usar Conjunto de Dados Existente" @@ -70,6 +77,9 @@ "deletedSuccessfully": "Previsão excluída com sucesso", "downloadedSuccessfully": "Previsão baixada com sucesso", "predictionCompleted": "Previsão concluída com sucesso", - "predictionJobSubmitted": "Trabalho de previsão enviado com sucesso" + "predictionJobSubmitted": "Trabalho de previsão enviado com sucesso", + "predictionsDeleted_one": "{{count}} previsão excluída com sucesso", + "predictionsDeleted_many": "{{count}} previsões excluídas com sucesso", + "predictionsDeleted_other": "{{count}} previsões excluídas com sucesso" } } diff --git a/DashAI/front/src/utils/i18n/locales/zh/common.json b/DashAI/front/src/utils/i18n/locales/zh/common.json index 529d2c2de..5c1e1b387 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/common.json +++ b/DashAI/front/src/utils/i18n/locales/zh/common.json @@ -13,6 +13,7 @@ "availableItems": "可用项目", "back": "返回", "cancel": "取消", + "clear": "清除", "close": "关闭", "columns": "列", "configureSubmodel": "配置子模型", @@ -112,6 +113,8 @@ "selectTask": "选择任务", "send": "发送", "sending": "发送中...", + "runInformation": "运行信息", + "session": "会话", "sessionInformation": "会话信息", "sessions": "会话", "skipTour": "跳过引导", @@ -141,6 +144,8 @@ "type": "类型", "unknown": "未知", "unknownError": "未知错误", + "yes": "是", + "no": "否", "update": "更新", "upload": "上传", "user": "用户", @@ -160,6 +165,18 @@ "columnNameInvalidCharacters": "列名只能包含字母、数字和下划线", "columnNameAlreadyExists": "已存在同名列", "errorRenamingColumn": "重命名列时出错", + "componentDownload": { + "download": "下载 ({{size}})", + "delete": "删除下载", + "deleteWithSize": "删除下载({{size}})", + "downloading": "下载中...", + "done": "组件已下载", + "deleted": "下载已删除", + "failed": "组件下载失败", + "mustDownload": "使用前必须先下载此模型", + "mustDownloadNested": "必须先下载这些组件:{{names}}", + "confirmDelete": "删除 {{name}} 的已下载文件?之后可以重新下载。" + }, "jobQueue": { "title": "任务队列", "refresh": "刷新", @@ -186,6 +203,7 @@ "jobType": "任务类型", "lastUpdated": "最后更新", "errorMessage": "错误信息", + "progress": "进度", "unnamedJob": "未命名任务" }, "status": { diff --git a/DashAI/front/src/utils/i18n/locales/zh/custom.json b/DashAI/front/src/utils/i18n/locales/zh/custom.json index 3f033a19d..2b09eb0ea 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/custom.json +++ b/DashAI/front/src/utils/i18n/locales/zh/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "选择一个项目以查看描述。", "selectInferenceMethods": "选择您想要应用的推理方法", "search": "搜索", + "modelSize": "大小:{{size}}", "noItemsFound": "未找到组件", "tryAdjustingSearch": "尝试调整搜索条件或筛选器", "componentsAvailable_one": "{{count}} 个可用组件", diff --git a/DashAI/front/src/utils/i18n/locales/zh/datasets.json b/DashAI/front/src/utils/i18n/locales/zh/datasets.json index 5fe4078f2..7b69e54e8 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/zh/datasets.json @@ -92,7 +92,8 @@ "requiresExactColumns_other": "需要恰好 {{required}} 个有效列,但只有 {{available}} 个可用。", "requiresMinColumns_one": "至少需要 {{required}} 个有效列,但只有 {{available}} 个可用。", "requiresMinColumns_other": "至少需要 {{required}} 个有效列,但只有 {{available}} 个可用。", - "zipContentsNotCompatible": "ZIP 文件不包含与所选数据加载器兼容的文件" + "zipContentsNotCompatible": "ZIP 文件不包含与所选数据加载器兼容的文件", + "cannotSaveEmptyDataset": "无法保存数据集:所有列已被删除。" }, "label": { "task": "任务", @@ -255,6 +256,11 @@ "margins": "边距", "marginTop": "顶部边距", "markerColor": "标记颜色", + "lineColor": "线条颜色", + "increasingColor": "上升颜色", + "decreasingColor": "下降颜色", + "totalsColor": "总计颜色", + "transparent": "透明", "max": "最大值", "maxLength": "最大长度", "mean": "均值", @@ -419,7 +425,10 @@ "showAll": "显示全部", "hideAll": "隐藏全部", "noRows": "无行数据", + "loadingMore": "正在加载更多…", + "rowsLoaded_other": "已加载 {{count}} / {{total}} 行", "doubleClickToRename": "双击以重命名", + "predictedColumn": "预测", "sortedAsc": "升序排列 - 点击改为降序", "sortedDesc": "降序排列 - 点击取消排序", "clickToSort": "点击升序排列", diff --git a/DashAI/front/src/utils/i18n/locales/zh/explainers.json b/DashAI/front/src/utils/i18n/locales/zh/explainers.json index b5977b715..8939cd457 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/zh/explainers.json @@ -3,7 +3,14 @@ "addExplainer": "添加解释器", "backToExplainers": "返回解释器列表", "hidePlot": "隐藏图表", - "showPlot": "显示图表" + "showPlot": "显示图表", + "download": "下载", + "downloadPng": "下载为 PNG", + "downloadSvg": "下载为 SVG", + "editPlot": "编辑图表", + "resetPlot": "重置编辑", + "fullscreen": "全屏", + "close": "关闭" }, "error": { "explainerFailed": "解释器生成失败。", @@ -54,9 +61,23 @@ "selectDatasetToExplain": "选择包含待解释实例的数据集", "selectExplainer": "设置名称和解释器", "selectExplainerAndName": "选择解释器并输入名称", + "selectInstance": "选择一个实例", "explainerInProgress": "解释器运行中...", "splitSelectionSummary": "百分比:{{percentage}}% | 已选行数:{{rowsSelected}} / {{totalRows}}", - "searchExplainers": "搜索解释器..." + "searchExplainers": "搜索解释器...", + "sourceFromDataset": "来自数据集", + "sourceManualInput": "手动输入", + "enterInstancesManually": "输入要解释的实例", + "manualInputDescription": "为每个要解释的实例填写特征值。", + "explanationInfo": "解释信息", + "inputColumns": "输入列:", + "targetColumn": "目标列:", + "markRowsToExplain": "标记要解释的行", + "rowSelectionMode": "行选择模式", + "rowModePercentage": "百分比滑块", + "rowModeManual": "手动选择", + "shuffleRows": "打乱选中的行(随机抽样)", + "rowsSelectedManually": "手动选择的行数:{{selected}} / {{total}}" }, "message": { "explainerJobCompleted": "解释器 {{name}} 成功完成", diff --git a/DashAI/front/src/utils/i18n/locales/zh/generative.json b/DashAI/front/src/utils/i18n/locales/zh/generative.json index bd86dc015..093dfda13 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/generative.json +++ b/DashAI/front/src/utils/i18n/locales/zh/generative.json @@ -17,7 +17,8 @@ "nameRequired": "名称为必填项", "processError": "处理失败,正在删除...{{error}}", "sessionNameEmpty": "会话名称不能为空", - "sessionNameExists": "已存在同名会话" + "sessionNameExists": "已存在同名会话", + "modelSwitchFailed": "更改会话模型失败" }, "label": { "confirmDeleteSession": "确定要删除会话 \"{{name}}\" 吗?此操作无法撤销。", @@ -38,6 +39,8 @@ "startBySelectingATask": "从选择生成式任务开始。", "noSessionsFound": "未找到会话", "parameterChangeEvent": "参数已更新:<1>", + "modelChangeEvent": "模型已更改:<1>", + "modelNotDownloaded": "该会话的模型尚未下载。请先下载后再继续。", "parameterChangeHistory": "当前会话的参数变更历史", "searchSessions": "搜索会话", "selectGenerativeTask": "选择生成式任务以开始新会话", @@ -57,7 +60,9 @@ "attachMedia": "附加媒体", "attachMediaToContinue": "附加媒体以继续", "noInputAvailable": "此任务无可用输入", - "selectModelCrumb": "选择模型" + "selectModelCrumb": "选择模型", + "sessionModel": "模型", + "downloadRequired": "需下载" }, "message": { "sessionCreatedSuccess": "会话创建成功。", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index 666fdfe2f..b8b3932fd 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -5,6 +5,7 @@ "createLocalExplainer": "新建局部解释器", "createPrediction": "创建预测", "createSession": "创建会话", + "addNewPrediction": "添加新预测", "deleteAndRetrain": "删除并重新训练", "deleteRun": "删除运行", "hideOperations": "隐藏操作", @@ -61,14 +62,22 @@ "label": { "addModelToSession": "将模型添加到会话", "confirmDeleteSession": "确定要删除会话 \"{{name}}\" 吗?此操作无法撤销。", + "availableExplainers": "可用解释器", "availableModels": "可用模型", + "modelCount_one": "{{count}} 个模型", + "modelCount_other": "{{count}} 个模型", "bar": "条形图", "heatmap": "热图", "chooseTaskForSessionWithDataset": "为使用数据集 \"{{datasetName}}\" 的会话选择机器学习任务。", + "configuration": "配置", + "modelConfiguration": "模型配置", + "sessionConfiguration": "会话配置", "configureModel": "配置模型", "configureOptimizer": "配置优化器", "configureSession": "配置会话", "configureTasksTrainCompareModels": "在有组织的会话中配置任务、训练并比较模型。选择一个任务开始建模工作流。", + "inputColumns": "输入列", + "outputColumns": "输出列", "customMetrics": "自定义指标", "datasetPredictions": "数据集预测", "editRunParameters": "编辑参数并重新运行模型", @@ -91,25 +100,32 @@ "localExplainer": "局部解释器", "localExplainers": "局部解释器", "manualPredictions": "手动预测", + "manual": "手动", "metricsEmptyForDisplaySet": "{{set}} 的结果指标为空。", "metrics": "指标", "metricToOptimize": "待优化指标", + "searchMetric": "搜索指标...", "modelComparison": "模型比较", "modelsModule": "模型模块", "nameYourSession": "为会话命名", "selectDatasetAndPrepare": "为会话命名,选择数据集并配置列和划分。", + "noCompatibleExplainersFound": "未找到兼容的解释器", "noCompatibleModelsFound": "未找到兼容模型", + "noConfigurationAvailable": "此运行没有可用的配置", "dropModelHere": "拖放此处以添加", + "noCompletedRuns": "暂无已完成的运行", "noDatasetPredictionsYet": "暂无数据集预测", "noGlobalExplainersYet": "暂无全局解释器", "noLocalExplainersYet": "暂无局部解释器", "noManualPredictionsYet": "暂无手动预测", "noMetricsAvailable": "暂无可用指标", "noMetricsAvailableForThisView": "此视图暂无可用指标", + "noExplainersMatchSearch": "没有解释器匹配您的搜索", "noModelsMatchSearch": "没有模型匹配您的搜索", "noPredictionsYet": "暂无预测记录", "noRunsYet": "暂无运行记录。请从右侧面板添加模型。", "noSessionSelected": "未选择会话", + "operations": "操作", "operationsWillBeDeletedWarning": "这些操作将被永久删除且无法恢复。确定要继续吗?", "optimizerConfiguration": "配置超参数优化器设置", "optimizerParameters": "优化器参数", @@ -125,9 +141,11 @@ "retrainWillDeleteOperations": "此运行有将被删除的现有操作", "retrainWillDeleteOperationsDetails": "重新训练运行 \"<1>{{runName}}\" 将删除:", "saveParameterChanges": "保存参数更改?", + "saveConfirmDetails": "保存“<1>{{runName}}”将把其状态重置为“未开始”,并清除其当前的指标和结果。确定要继续吗?", "showResults": "显示结果", "saveWillDeleteOperationsDetails": "保存 \"<1>{{runName}}\" 将重置该运行。再次训练时以下内容将被删除:", "runDetails": "运行详情", + "runNotFound": "未找到该运行", "runExperimentToSeeMetrics": "前往<1>实验标签页运行您的实验。", "runFailedNoHyperparameterPlots": "运行失败。无超参数图表可用。", "runInProgressCannotEdit": "运行正在进行中,无法编辑。", @@ -137,6 +155,7 @@ "searchModels": "搜索模型...", "selectDataset": "选择数据集", "selectDatasetForSession": "为会话选择数据集", + "exitModelDetailToAddModels": "返回会话概览以添加更多模型。", "selectSessionToViewModels": "选择会话以查看可用模型。", "selectTask": "选择任务", "selectTaskForSession": "为会话选择任务", @@ -158,26 +177,14 @@ "higherIsBetter": "越高越好", "lowerIsBetter": "越低越好", "bestModel": "最优", - "score": "评分", - "scoreProfile": "评分档案", - "scoreHeaderTooltip": "基于所选档案的 0–100 加权评分。无界误差指标(MAE、RMSE、TER 等)相对于此比较中最差模型进行归一化。悬停在单元格上可查看详情。", - "profile_balanced": "均衡", - "profile_detectPositives": "检测正例", - "profile_avoidFalseAlarms": "避免误报", - "profile_probabilityQuality": "概率质量", - "profile_regression_fit": "模型拟合", - "profile_regression_error": "误差均衡", - "profile_translation_quality": "翻译质量", - "profile_translation_balanced": "翻译均衡", - "profile_text_balanced": "均衡", - "profile_text_detectPositives": "检测正例", - "profile_text_avoidFalseAlarms": "避免误报", - "profile_text_probabilityQuality": "概率质量", "editRun": "编辑运行", "chartType": "图表类型" }, "message": { + "skippedUndownloadedRuns": "已跳过模型未下载的运行。请先下载模型。", "allRunsCompleted": "{{experiment}} 已完成所有运行。", + "tabAvailableAfterFinish": "运行训练完成后即可使用。", + "noOptimizableParamsForHpo": "没有参数被标记为优化——请编辑运行的配置以启用此功能。", "confirmDeleteRun": "确定要删除此运行吗?此操作无法撤销。", "editingParametersWarning": "编辑参数将把此运行的状态重置为「未开始」。所有现有指标和结果将丢失。", "aboutToUpdateParameters": "您即将更新运行 {{runName}} 的参数。此操作将产生以下后果:", diff --git a/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json index 471b7d17c..77d88ede8 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>您的模型运行卡片<1>完美!此卡片包含关于您模型运行的所有信息。您可以:<2><0><0>训练:使用已配置的参数开始训练过程<1><0>查看指标:训练完成后查看性能分数<2><0>进行预测:使用训练好的模型对新数据进行预测<3><0>创建解释器:了解您的模型如何做出决策<3>让我们训练此模型,看看它的表现!", "performanceVisualizations": "<0><0>性能可视化<1>图表视图显示性能指标和其他可视化内容,帮助您更好地理解模型性能。<2>🎉 <0>干得好!您现在可以添加更多模型并尝试不同参数!", "sessionVisualization": "<0><0>会话可视化<1>欢迎来到会话可视化!这是您可以比较不同模型、训练并分析其性能的地方。<2>让我们从向此会话添加一些模型开始比较。", - "trainModel": "<0><0>训练您的模型<1>点击<0>训练按钮使用已配置的参数开始训练模型。<2>训练过程将在后台运行,您将在此处看到进度和结果。<3><0>点击「训练」继续!", - "visualizeResults": "<0><0>可视化结果<1>想以更直观的方式查看结果?点击<0>图表按钮从表格视图切换到交互式图表。<2><0>点击「图表」继续!" + "trainModel": "<0><0>训练您的模型<1>点击<0>训练按钮使用已配置的参数开始训练模型。<2>训练过程将在后台运行,您将在此处看到进度和结果。<3><0>点击「训练」继续!" } diff --git a/DashAI/front/src/utils/i18n/locales/zh/prediction.json b/DashAI/front/src/utils/i18n/locales/zh/prediction.json index e89bf2fde..d9e77bdb2 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/prediction.json +++ b/DashAI/front/src/utils/i18n/locales/zh/prediction.json @@ -1,5 +1,6 @@ { "button": { + "deleteSelected_other": "删除所选({{count}})", "downloadResults": "下载预测结果", "hideResults": "隐藏结果", "runPrediction": "运行预测", @@ -9,6 +10,7 @@ "error": { "creatingPrediction": "创建预测时出错", "errorDeleting": "删除预测时出错", + "errorDeletingSelected": "删除所选预测时出错", "errorDownloading": "下载预测时出错", "fetchingDatasets": "获取预测用数据集时出错。", "loadingPredictionData": "加载预测数据时出错", @@ -25,6 +27,7 @@ "clickToViewOrDelete": "单击预测记录查看详情,或使用删除图标将其删除。", "configureInput": "配置输入", "confirm": "确认", + "confirmBulkDeletion_other": "确定要删除选中的 {{count}} 条预测吗?此操作无法撤销。", "confirmDeletion": "确定要删除此预测吗?此操作无法撤销。", "confirmDeletionTitle": "删除预测?", "confirmPrediction": "确认预测", @@ -52,7 +55,6 @@ "previewResults": "预览结果", "previousPredictions": "历史预测", "provideManualInput": "手动输入数据进行预测。请填写下方字段,准备好后提交。", - "resultsPreview": "显示前 100 行预览。下载 CSV 获取完整结果。", "resultsPreviewDownloadInfo": "下表显示预测结果的预览。您可以使用下方的「下载 CSV」按钮下载完整结果。", "reviewDetails": "在提交前检查您的预测配置", "runFirstPrediction": "运行第一次预测以在此处查看结果", @@ -62,6 +64,7 @@ "selectDataset": "选择用于预测的数据集", "selectDatasetFromPlatform": "从平台选择数据集", "selectMode": "选择模式", + "selectRowsToDelete": "选择要删除的行", "selectPredictionMode": "选择为预测提供输入数据的方式", "targetColumn": "目标列", "useExistingDataset": "使用现有数据集" @@ -70,6 +73,7 @@ "deletedSuccessfully": "预测删除成功", "downloadedSuccessfully": "预测下载成功", "predictionCompleted": "预测成功完成", - "predictionJobSubmitted": "预测任务提交成功" + "predictionJobSubmitted": "预测任务提交成功", + "predictionsDeleted_other": "已成功删除 {{count}} 条预测" } } diff --git a/DashAI/front/src/utils/jobPoller.js b/DashAI/front/src/utils/jobPoller.js index 2a981e372..5f0fc7dba 100644 --- a/DashAI/front/src/utils/jobPoller.js +++ b/DashAI/front/src/utils/jobPoller.js @@ -125,13 +125,23 @@ async function pollJobs() { const job = allJobs.find((j) => j.id === jobId); if (job?.status === "finished") { if (watcher.onSuccess) watcher.onSuccess(job); - } else { + stopJobPolling(jobId); + } else if (job?.status === "error") { + if (watcher.onError) watcher.onError(job); + stopJobPolling(jobId); + } else if (!job) { + // The job vanished from the queue entirely; treat it as failed. if (watcher.onError) - watcher.onError(job || { id: jobId, status: "deleted" }); + watcher.onError({ id: jobId, status: "deleted" }); + stopJobPolling(jobId); } + // A job still pending or running is left watched and rechecked on the + // next poll, so a freshly queued job is never reported as an error + // just because the queue briefly looked empty. + } + if (state.jobWatchers.size === 0) { + stopJobPoller(); } - state.jobWatchers.clear(); - stopJobPoller(); return; } catch (e) { console.error("[JobPoller] Final flush failed, will retry:", e); diff --git a/DashAI/front/src/utils/runStatus.js b/DashAI/front/src/utils/runStatus.js index 9bb93c0cf..80e312af0 100644 --- a/DashAI/front/src/utils/runStatus.js +++ b/DashAI/front/src/utils/runStatus.js @@ -14,3 +14,29 @@ export function getRunStatus(statusNumber, t) { throw new Error(`Error ${statusNumber} is not a valid status`); } } + +export function getRunStatusColor(statusNumber) { + switch (statusNumber) { + case 0: + return "default"; + case 1: + case 2: + return "info"; + case 3: + return "success"; + case 4: + return "error"; + default: + return "default"; + } +} + +// Not Started, Finished, or Error — anything eligible for a (re)train action. +export function canTrainRun(statusNumber) { + return statusNumber === 0 || statusNumber === 3 || statusNumber === 4; +} + +// Delivered or Started — currently executing, nothing new can be triggered. +export function isRunActive(statusNumber) { + return statusNumber === 1 || statusNumber === 2; +} diff --git a/DashAI/front/yarn.lock b/DashAI/front/yarn.lock index 132aafbbe..2f851508f 100644 --- a/DashAI/front/yarn.lock +++ b/DashAI/front/yarn.lock @@ -1817,6 +1817,19 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" + dependencies: + "@dnd-kit/utilities": ^3.2.2 + tslib: ^2.0.0 + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: c853cb65d2ffb3d58d400d9f1c993b00413932acf5cf5b780c76acf3b1057aa88e7866021c6b178c4b33fc17db7fe7640584dba4449772e02edcb72cc797eeb0 + languageName: node + linkType: hard + "@dnd-kit/utilities@npm:^3.2.2": version: 3.2.2 resolution: "@dnd-kit/utilities@npm:3.2.2" @@ -7992,6 +8005,8 @@ __metadata: resolution: "dashai-frontend@workspace:." dependencies: "@dnd-kit/core": ^6.3.1 + "@dnd-kit/sortable": ^10.0.0 + "@dnd-kit/utilities": ^3.2.2 "@emotion/react": ^11.10.6 "@emotion/styled": ^11.10.6 "@mui/icons-material": ^7 diff --git a/Dockerfile b/Dockerfile index cb6056db5..0cafda4b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,12 @@ RUN corepack enable && yarn install --frozen-lockfile && yarn build # Stage 2: Python backend serving the built frontend FROM python:3.11-slim +COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /uvx /bin/ WORKDIR /app COPY . . COPY --from=frontend /app/DashAI/front/build DashAI/front/build -RUN pip install --no-cache-dir -r requirements-cpu.txt && \ - pip install --no-cache-dir --no-deps -e . +RUN uv sync --locked --extra cpu --no-dev --no-cache +ENV PATH="/app/.venv/bin:$PATH" ENV DASHAI_HOST=0.0.0.0 EXPOSE 8000 CMD ["python", "-m", "DashAI", "--no-browser"] diff --git a/Dockerfile.cuda b/Dockerfile.cuda index c819bbcb8..067728548 100644 --- a/Dockerfile.cuda +++ b/Dockerfile.cuda @@ -25,23 +25,25 @@ RUN apt-get update && apt-get install -y \ RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so && \ ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so.1 -# requirements-cuda.txt = cu128 torch wheels + CUDA-compiled llama-cpp-python -# (it -r includes requirements.txt, so copy both) -COPY requirements.txt requirements-cuda.txt ./ -RUN pip install --upgrade pip && \ - pip install --no-cache-dir -r requirements-cuda.txt +COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /bin/uv + +# cuda extra = cu128 torch wheels + llama-cpp-python compiled with CUDA offload +COPY pyproject.toml uv.lock ./ +ENV UV_PROJECT_ENVIRONMENT=/opt/venv +ENV CMAKE_ARGS="-DGGML_CUDA=on" +RUN uv sync --locked --extra cuda --no-dev --no-install-project --no-cache # -------- Stage 3: runtime (CUDA runtime, no build tools) -------- FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 WORKDIR /app RUN apt-get update && apt-get install -y \ - python3 python3-pip \ + python3 \ && rm -rf /var/lib/apt/lists/* -# copy installed python env only (not build tools) -COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages -COPY --from=builder /usr/local/bin /usr/local/bin +# copy the resolved virtualenv only (not build tools) +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" # application code COPY . . diff --git a/MANIFEST.in b/MANIFEST.in index 046baa111..9ce4c8b82 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,3 @@ -include requirements.txt -include requirements-dev.txt include DashAI/alembic.ini recursive-include DashAI/alembic * recursive-include DashAI/front/build * diff --git a/README.rst b/README.rst index 4834973fd..f0fa283dd 100644 --- a/README.rst +++ b/README.rst @@ -61,8 +61,21 @@ Installation (PyPI) =================== dashAI needs Python 3.10 or greater. We strongly recommend installing it inside -an isolated environment (``venv`` or ``conda``) to avoid clashes with other -packages. +an isolated environment to avoid clashes with other packages. The quickest way +to do that is with `uv `_ +(recommended, it even installs Python for you); classic ``venv``/``conda`` with +``pip`` works exactly the same if you prefer it. + +**Shortcut:** if you just want dashAI as an app with the default PyTorch build +for your platform, uv can install it in its own isolated environment and put +the ``dashai`` command on your PATH in one line: + +.. code:: bash + + $ uv tool install dashai + +For GPU acceleration, a CPU-slim install, or LLM (GGUF) support, follow the +steps below instead. Installing dashAI also installs PyTorch with the default build for your platform, which works out of the box on CPU. To enable GPU acceleration (NVIDIA @@ -75,6 +88,13 @@ never installed automatically, so install it in step 3 if you need those models. 1. Create an environment ------------------------- +**Any OS (uv, recommended)** + +.. code:: bash + + $ uv venv --python 3.12 + $ source .venv/bin/activate # Windows: .venv\Scripts\activate + **Linux / macOS (venv)** .. code:: bash @@ -104,6 +124,9 @@ With the environment active: .. code:: bash + $ uv pip install dashai + + # or, with plain pip: $ pip install dashai @@ -113,6 +136,9 @@ With the environment active: This step is optional on CPU (step 2 already installed a working PyTorch). Run the section below that matches your hardware to pick a specific build. +Every ``pip install`` command below can also be run as ``uv pip install`` with +the same flags: same result, just faster. + CPU only (Linux / macOS / Windows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -181,7 +207,7 @@ Replace ```` with your CUDA tag. Prebuilt wheels are published for for the available wheels and other backend options. -1. Run dashAI +4. Run dashAI ------------- Start the server and graphical interface with: @@ -311,39 +337,73 @@ Backend Prepare the environment ~~~~~~~~~~~~~~~~~~~~~~~ -First, set the python enviroment, for that you can use -`conda `_: +Dependencies are managed with `uv `_. Install it +following the `official instructions `_, +then install the project (uv creates the virtualenv, installs the package in +editable mode and all development dependencies): -.. code: bash +.. code:: bash - $ conda create -n dashai python=3.12 - $ conda activate dashai + $ uv sync + $ uv run pre-commit install -Later, install the requirements: +On machines without an NVIDIA GPU you can use the much lighter CPU-only +PyTorch wheels instead: .. code:: bash - $ pip install -r requirements.txt - $ pip install -r requirements-dev.txt + $ uv sync --extra cpu + +On NVIDIA machines, the ``cuda`` extra pins the CUDA 12.8 PyTorch wheels. To +also get LLM (GGUF) support with CUDA offload, set ``CMAKE_ARGS`` so that +``llama-cpp-python`` compiles against CUDA (this requires CMake, a C compiler +and the CUDA toolkit; see "Build tools for GPU llama-cpp" above): + +.. code:: bash + + $ uv cache clean llama-cpp-python + $ CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python + +The first command and the ``--reinstall-package`` flag matter: uv skips +packages that are already installed and caches built wheels, and neither +check looks at ``CMAKE_ARGS``, so without them a previous CPU build gets +silently reused. Without ``CMAKE_ARGS``, ``llama-cpp-python`` still installs +but runs on CPU (there is no prebuilt CUDA wheel on PyPI). If nvcc rejects +your default gcc as too new, point it at an older one you have installed, +for example ``CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"``. + +If you prefer plain ``pip``, the same setup works inside any environment +(``venv`` or ``conda``) since all metadata lives in ``pyproject.toml``. Note +that this skips the lockfile, so versions may differ slightly from the ones +the team and CI use: + +.. code:: bash + + $ pip install -e . --group dev # --group needs pip >= 25.1 $ pre-commit install Running the Backend ~~~~~~~~~~~~~~~~~~~ -There are two ways to run dashAI: - -1. By executing dashAI as a module from the root of the repository: +There are two ways to run dashAI from the root of the repository: .. code:: bash - $ python -m DashAI + $ uv run python -m DashAI -2. Or, installing the default build: +Or, through the installed entry point: .. code:: bash - $ pip install . -e - $ dashai + $ uv run dashai + +**Important:** if you synced with an extra, pass the same extra to ``uv run`` +(for example ``uv run --extra cpu python -m DashAI``). A plain ``uv run`` +re-syncs the environment to the default set and swaps your PyTorch build back +to the PyPI one. + +(If you installed with pip inside your own environment, drop the ``uv run`` +prefix: ``python -m DashAI`` or ``dashai``.) Optional Flags @@ -528,6 +588,7 @@ Acknowledgments This project is developed in collaboration with: * `University of Chile `_ - Leading Institution +* `Fcfm `_ - Leading Institution * `CENIA `_ - Associated Institution * `IMFD `_ - Collaborator * `Unholster `_ - Industry Partner diff --git a/docs/docs/build/dev-setup.md b/docs/docs/build/dev-setup.md index ec56fe33e..71036abe0 100644 --- a/docs/docs/build/dev-setup.md +++ b/docs/docs/build/dev-setup.md @@ -8,7 +8,7 @@ sidebar_position: 2 ## Prerequisites -- Python 3.10 to 3.13 +- [uv](https://docs.astral.sh/uv/getting-started/installation/) (manages Python and dependencies; Python 3.10 to 3.13) - Node.js (LTS) and Yarn 3.5.0 - Git @@ -22,22 +22,50 @@ git checkout develop ## 2. Backend Setup -Create and activate a Python environment (conda or venv): +Install all dependencies (uv creates the `.venv` and installs the package +in editable mode, including development dependencies): ```bash -conda create -n dashai python=3.10 -conda activate dashai +uv sync +uv run pre-commit install ``` -Install the package in editable mode with development dependencies: +On machines without an NVIDIA GPU you can use the CPU-only PyTorch wheels, +which are much lighter: ```bash -pip install -r requirements.txt -pip install -e . -pip install -r requirements-dev.txt +uv sync --extra cpu +``` + +On NVIDIA machines, the `cuda` extra pins the CUDA 12.8 PyTorch wheels. To +also get LLM (GGUF) support with CUDA offload, set `CMAKE_ARGS` so that +`llama-cpp-python` compiles against CUDA (requires CMake, a C compiler and +the CUDA toolkit): + +```bash +uv cache clean llama-cpp-python +CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python +``` + +The first command and the `--reinstall-package` flag matter: uv skips +packages that are already installed and caches built wheels, and neither +check looks at `CMAKE_ARGS`, so without them a previous CPU build gets +silently reused. Without `CMAKE_ARGS`, `llama-cpp-python` still installs but +runs on CPU (there is no prebuilt CUDA wheel on PyPI). If nvcc rejects your +default gcc as too new, point it at an older one you have installed, for +example `CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"`. + +Alternatively, plain `pip` works inside any environment (venv or conda), +since all metadata lives in `pyproject.toml`. Note this skips the lockfile, +so versions may differ slightly from the ones the team and CI use: + +```bash +pip install -e . --group dev # --group needs pip >= 25.1 pre-commit install ``` +If you go the pip route, drop the `uv run` prefix from the commands below. + ## 3. Frontend Setup ```bash @@ -50,11 +78,16 @@ yarn install **Backend** (from the repo root): ```bash -python -m DashAI +uv run python -m DashAI # or -dashai --no-browser --logging-level INFO +uv run dashai --no-browser --logging-level INFO ``` +**Important:** if you synced with an extra, pass the same extra to `uv run` +(for example `uv run --extra cpu python -m DashAI`). A plain `uv run` re-syncs +the environment to the default set and swaps your PyTorch build back to the +PyPI one. + **Frontend** (development server with hot reload): ```bash @@ -69,8 +102,8 @@ The backend runs at `http://localhost:8000` and the frontend dev server at `http **Python** (using Ruff): ```bash -ruff check . --fix -ruff format . +uv run ruff check . --fix +uv run ruff format . ``` **Frontend** (ESLint + Prettier): @@ -86,10 +119,10 @@ dashAI uses pre-commit hooks for consistent code quality: ```bash # Run all hooks manually -pre-commit run --all-files +uv run pre-commit run --all-files # Run on staged files (happens automatically on git commit) -pre-commit run +uv run pre-commit run ``` ## Project Structure diff --git a/docs/docs/deep-dive/benchmark.md b/docs/docs/deep-dive/benchmark.md deleted file mode 100644 index 69c21053f..000000000 --- a/docs/docs/deep-dive/benchmark.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: Comparative Benchmark -description: Comparison of dashAI with KNIME, Orange, and WEKA on licensing, extensibility, and task coverage. -sidebar_label: Comparative Benchmark ---- - -# Comparative Benchmark - -This section compares dashAI with KNIME Analytics Platform, Orange Data Mining, and WEKA, the leading open source no code ML platforms with comparable architecture. - ---- - -## Methodology - -The platform subset was scoped to tools that simultaneously meet three conditions: open source distribution, code free operation for the end user, and an extensible catalog architecture by third parties. KNIME Analytics Platform, Orange Data Mining, and WEKA satisfy that criterion alongside dashAI. - -The comparison is structured along three measurable dimensions: licensing terms, architectural cost of extension, and paradigmatic coverage of the native catalog. For the extensibility dimension, the reported boilerplate corresponds to reference components implemented on each platform from their official repositories; sources are listed at the end of this page. - ---- - -## Licensing - -dashAI is distributed under the **MIT** license, allowing use, modification, and distribution without restrictions in commercial or institutional projects. - -KNIME and Orange use GPLv3; WEKA, GPL. All three licenses include copyleft on distributed derivative works. KNIME also presents additional features (scheduling, governed deployment, RBAC, and the AI Extension) available only through KNIME Business Hub under commercial licensing. - ---- - -## Extensibility - -dashAI exposes twelve base classes organized by functional role: `BaseModel`, `BaseMetric`, `BaseTask`, `BaseExplainer`, among others. A new component is implemented by subclassing the corresponding abstraction and declaring its parameters via a Pydantic schema. From the schema, the platform automatically generates the configuration form in the interface, without requiring any frontend code. The component is distributed via PyPI and installed directly from the dashAI interface. - -The resulting boilerplate is approximately **20-40 lines of code** for simple components (metrics, basic classifiers) and **70-110 lines** for models with multiple hyperparameters, where most of the code corresponds to the parameter schema declaration. - -For reference, the extension mechanisms of the other evaluated platforms are as follows: - -- **Orange** allows Python extensions, but each widget must couple to Qt/PyQt and manually instantiate interface controls (≈50-70 lines of code). -- **WEKA** is extended in Java by inheriting from `AbstractClassifier` and implementing `buildClassifier(Instances)` and `distributionForInstance(Instance)` over the `Instances` abstraction (≈80-120 lines of code). -- **KNIME**, via the official path, requires an OSGi/Eclipse plugin with four Java classes (`NodeFactory`, `NodeModel`, `NodeDialog`, and `NodeView`), plus `plugin.xml` and `MANIFEST.MF` descriptors, and a Maven/Tycho build (≈150-250 lines of code). Since version 4.6 there is an experimental Python path (Labs) that generates UI from parameter declarations, but it does not replace the Java path as the official approach and requires its own packaging tools (`pixi`, `knime.yml`). - -### Interface Architecture - -dashAI adopts a client server architecture: a FastAPI server exposes the component catalog, datasets, training jobs, and results; a React frontend consumes that API via HTTP. When a new component is registered, the server exposes its JSON schema and the frontend renders the configuration form without prior knowledge of the component. The server can run on any machine and be accessed from a browser, even from another device on the same local network. - -KNIME is built on Eclipse RCP, Orange on PyQt, and WEKA on Java Swing. In all three cases the interface and business logic run in the same process; adding a new component also requires modifying the presentation layer. - ---- - -## Task Coverage - -dashAI's native catalog covers four predictive tasks (tabular classification, regression, text classification, and translation) with approximately fifteen models each, along with five LLMs and eleven image generation models, all runnable locally. Hyperparameter optimization integrates Optuna and HyperOpt. - -The interface is available in Spanish and English. - ---- - -## Comparison Table - -| Criterion | KNIME | Orange | WEKA | **dashAI** | -| ------------------------------- | :-----------------------------: | :----------------: | :------------------: | :-----------------------: | -| **Licensing** | | | | | -| License | GPLv3 | GPLv3 | GPL | **MIT** | -| No production paywall | No | Yes | Yes | **Yes** | -| **Extensibility** | | | | | -| Extension language | Java (official) / Python (Labs) | Python + Qt/PyQt | Java | **Python** | -| Abstractions by functional role | 1 (generic node) | 1 (generic widget) | 6 Java hierarchies | **12 base classes** | -| Interface type | Desktop (Eclipse) | Desktop (PyQt) | Desktop (Java Swing) | **Web (React + FastAPI)** | -| Autogenerated UI | Partial | No | No | **Yes** | -| Boilerplate per component | 150-250 LoC (Java) | 50-70 LoC | 80-120 LoC | **20-40 LoC** | -| GPU support | Partial | Partial | Partial | **Partial** | -| Multilingual interface (ES/EN) | No | No | No | **Yes** | -| **Native Catalog** | | | | | -| Tabular classification models | ~11 | ~12 | ~39 | **15** | -| Regression models | ~9 | ~12 | ~32 | **15** | -| Text classification models | Partial | Partial | 33 (BoW) | **15** | -| Translation models | Partial | No | No | **9** | -| LLMs run locally | No | No | No | **5** | -| Image generation models | Partial | No | No | **11** | -| Integrated HPO frameworks | 1 | 0 | 1 | **2** | - -**Legend:** Yes = full native support, Partial = partial or requires additional extensions, No = not supported, LoC = lines of code. - -:::note Autogenerated UI -The official KNIME path (Java) requires programming `NodeDialog` and `NodeView` manually. The experimental Python path (Labs, since v4.6) generates UI from parameter declarations, but it is not the official path. Orange and WEKA require manual interface code in all cases. -::: - -:::note Boilerplate per component -The 150-250 LoC for KNIME correspond to the official Java path. The Python path (Labs) reduces that number but adds its own configuration files (`knime.yml`, `pixi.toml`). In dashAI: ≈20-40 LoC for simple components; ≈70-110 LoC for complex models with multiple hyperparameters, where most of the code is the parameter schema declaration. -::: - -:::note Task overlap (dashAI) -9 of the 15 tabular classification models have a regression counterpart (Random Forest, Gradient Boosting, SVM, among others); each task has its own independent implementation and configuration. In WEKA, regression and classification both inherit from `AbstractClassifier` and are differentiated by capability flag. -::: - ---- - -## Sources - -- [knime-python-extension-template: `extension.py`](https://github.com/knime-oss/knime-python-extension-template/blob/releases/2025-12/src/extension.py) -- [Orange3: `OWDataSamplerA.py`](https://github.com/biolab/orange3/blob/master/doc/development/source/orange-demo/orangedemo/OWDataSamplerA.py) -- [knime-examples: `UnitConverterNodeModel.java`](https://github.com/knime-oss/knime-examples/blob/master/org.knime.examples.unitconverter/src/org/knime/examples/unitconverter/UnitConverterNodeModel.java) -- [weka-3.8: `NBTree.java`](https://github.com/Waikato/weka-3.8/blob/master/packages/internal/naiveBayesTree/src/main/java/weka/classifiers/trees/NBTree.java) -- [weka-3.8: `SMOTE.java`](https://github.com/Waikato/weka-3.8/blob/master/packages/external/SMOTE/src/main/java/weka/filters/supervised/instance/SMOTE.java) diff --git a/docs/docs/deep-dive/comparison.md b/docs/docs/deep-dive/comparison.md new file mode 100644 index 000000000..e92465ac3 --- /dev/null +++ b/docs/docs/deep-dive/comparison.md @@ -0,0 +1,154 @@ +--- +title: Comparison with Existing Platforms +description: Comparison of dashAI with KNIME, Orange, and WEKA on licensing, extensibility, and task coverage. +sidebar_label: Comparison +--- + +# Comparison with Existing Platforms + +This section compares dashAI with KNIME Analytics Platform, Orange Data Mining, and WEKA, three open-source, no-code machine learning platforms that meet the selection criteria described in the Methodology section below. + +--- + +## Methodology + +The set of platforms considered was restricted to tools that jointly satisfy three conditions: open-source distribution, code-free operation for the end user, and a catalog architecture that can be extended by third parties. KNIME Analytics Platform, Orange Data Mining, and WEKA satisfy these conditions alongside dashAI. + +The comparison is organized along three measurable dimensions: licensing terms, extension mechanism, and the task-paradigm coverage of the native catalog. For the licensing and extensibility dimensions, each claim below is linked directly to its primary source (official license text, official developer documentation, or the corresponding source file), either inline or in the [Sources](#sources) section. For the task-coverage dimension, catalog counts were obtained through direct inspection of each platform's own interface (node repository, widget catalog, package manager, or model catalog) rather than from a single external listing, since none of the four platforms publishes a canonical count broken down in this way; see [Sources](#sources) for further detail. + +--- + +## Licensing + +DashAI is distributed under the **[MIT license](https://docs.dash-ai.com/discover/overview/)**, which permits use, modification, and redistribution—including in commercial or institutional settings—subject to retention of the original copyright and license notice. + +[KNIME](https://www.knime.com/downloads/full-license) and [Orange](https://orangedatamining.com/license/) are distributed under GPLv3; [WEKA](https://waikato.github.io/weka-wiki/faqs/commercial_applications/), under GPL. All three licenses impose copyleft obligations on distributed derivative works. + +KNIME's desktop client, the KNIME Analytics Platform, is free and open-source under GPLv3, and this includes its officially maintained [AI Extension](https://docs.knime.com/latest/analytics_platform_ai_extension_guide/), which provides both cloud-API and local (GPT4All/Ollama) LLM access at no cost. KNIME's commercial licensing applies only to server-side enterprise deployment through [KNIME Business Hub](https://www.knime.com/knime-business-hub), which handles centralized scheduling, governed (web) deployment, and role-based access control (RBAC). + +--- + +## Extensibility + +DashAI exposes [twelve base classes](https://docs.dash-ai.com/deep-dive/components/), organized by functional role, including `BaseModel`, `BaseMetric`, `BaseTask`, and `BaseGlobalExplainer`. A new component is implemented by subclassing the corresponding base class and declaring its parameters through a Pydantic schema; [the platform derives the configuration form shown in the interface directly from this schema](https://docs.dash-ai.com/deep-dive/architecture/), without additional frontend code. The resulting component is packaged as a plugin and distributed via PyPI, then installed from within the dashAI interface. + +The extension mechanisms of the other three platforms are as follows: + +- **Orange** allows [Python extensions](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html), but each widget must couple to Qt/PyQt and [manually instantiate interface controls](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial-settings.html), since there is no schema-to-form autogeneration. +- **WEKA** follows a plugin architecture organized into major component families, including classifiers, clusterers, associators, filters, attribute selection methods, and data loaders. Each family exposes its own extension API through abstract base classes and/or interfaces (e.g., `AbstractClassifier` for classifiers and `Filter` for filters). New classifiers are typically implemented by extending `AbstractClassifier` and overriding `buildClassifier(Instances)` and `distributionForInstance(Instance)`, which operate on WEKA's `Instances` data abstraction. [WEKA's repository structure](https://github.com/Waikato/weka-3.8/tree/master), [AbstractClassifier Javadoc](https://weka.sourceforge.io/doc.dev/weka/classifiers/AbstractClassifier.html), and [Filter Javadoc](https://weka.sourceforge.io/doc.dev/weka/filters/Filter.html) document these extension points. +- **KNIME**, via the official path, requires an [OSGi/Eclipse plugin with four Java classes](https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/) (`NodeFactory`, `NodeModel`, `NodeDialog`, and `NodeView`), plus `plugin.xml` and `MANIFEST.MF` descriptors, and a Maven/Tycho build. Since version 4.6 there is an [experimental Python path (Labs)](https://docs.knime.com/latest/pure_python_node_extensions_guide/) that generates a dialog from parameter declarations, but it does not replace the Java path as the official approach and requires its own packaging tools (`pixi`, `knime.yml`). + +### Interface Architecture + +DashAI adopts a [client-server architecture](https://docs.dash-ai.com/deep-dive/architecture/): a FastAPI server exposes the component catalog, datasets, training jobs, and results, while a React frontend consumes that API via HTTP. This separation allows the backend to run on a different machine, including GPU-equipped servers, while users interact through a standard web browser without requiring local installation of the computational backend. Components expose their configuration through JSON schemas derived from Pydantic models, allowing the frontend to render configuration forms automatically. + +[KNIME is built on Eclipse RCP](https://www.knime.com/open-source-story), [Orange on PyQt](https://github.com/biolab/orange3), and [WEKA on Java Swing](https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html). In all three cases the interface and business logic execute within the same desktop application. While these architectures avoid the networking overhead of a client-server design, they do not provide browser-based access to a remotely hosted backend in their open-source editions. + +## Task Coverage + +DashAI's native catalog covers four predictive tasks: tabular classification, regression, and text classification, each with fifteen models, and translation, with nine. It additionally includes five large language models (LLMs) and eleven image generation models. All models in the native catalog run locally. Hyperparameter optimization is supported through two integrated frameworks, Optuna and HyperOpt. + +The interface is available in Spanish, English, Chinese, German, and Portuguese. + +### Local and API-based LLM Access on Competing Platforms + +KNIME's officially maintained [AI Extension](https://docs.knime.com/latest/analytics_platform_ai_extension_guide/) provides offline LLM execution through two paths: the [Local GPT4All LLM Selector](https://hub.knime.com/knime/extensions/org.knime.python.features.llm/latest/org.knime.python3.nodes.extension.ExtensionNodeSetFactory%24DynamicExtensionNodeFactory:8f5a8be6) node, which runs a user-supplied GGUF model file entirely offline, and an [OpenAI-compatible connector pointed at a local Ollama endpoint](https://www.knime.com/blog/how-to-leverage-open-source-llms-ollama). Because these are general-purpose connector nodes rather than a curated set of bundled models, they do not map onto a fixed "number of models" the way dashAI's five preloaded LLMs do; the user supplies the model file or Ollama endpoint themselves. + +We could not find an equivalent officially maintained Orange add-on for local LLM inference; community write-ups describe wiring Orange's general-purpose Python Script widget to a locally running Ollama or LM Studio server, which is a valid but code-based integration rather than a no-code building block, so Orange remains "No" on this dimension pending an official add-on. + +### Hyperparameter Optimization on Competing Platforms + +The single-integer HPO counts for KNIME and WEKA understate the number of available *tools* but are retained as counts of *default, no-code-ready mechanisms*, to keep the comparison meaningful against dashAI's built-in Optuna/HyperOpt selector: + +- **WEKA** ships [`CVParameterSelection`](https://waikato.github.io/weka-wiki/optimizing_parameters/) in its base distribution, performing cross-validated grid search over user-specified parameter ranges. `GridSearch` (nested-parameter support) ships only in WEKA's developer builds. Two further options—`MultiSearch` and [Auto-WEKA](https://www.jmlr.org/papers/v18/16-261.html), a combined algorithm-selection and hyperparameter-optimization (CASH) system using sequential model-based Bayesian optimization—are one-click installs from WEKA's built-in Package Manager, which ships with every WEKA distribution but requires the user to install the specific package. +- **KNIME** provides [Parameter Optimization Loop Start/End](https://hub.knime.com/knime/extensions/org.knime.features.optimization/latest/org.knime.optimization.internal.node.parameter.loopstart.LoopStartParOptNodeFactory) nodes supporting brute-force, hill-climbing, and random-search strategies over an arbitrary model-learner node. These ship as a small, official "KNIME Optimization" extension rather than in the default node repository. +- **Orange** includes a native [Parameter Fitter widget](https://orange3.readthedocs.io/projects/orange-visual-programming/en/master/widgets/evaluate/parameterfitter.html) in its default distribution. Its scope is narrow: it tunes a single integer hyperparameter, and only for Random Forest and PLS models. We revise Orange's entry from "No" to "Partial" to reflect this widget, while keeping the WEKA/KNIME entries at "1", since Auto-WEKA, MultiSearch, and the KNIME Optimization extension are additional installs, comparable in status to the AI Extension discussed above, rather than defaults. + +--- + +## Comparison Table + +| Criterion | KNIME | Orange | WEKA | **dashAI** | +| ------------------------------- | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | +| **Licensing** | | | | | +| License | [GPLv3](https://www.knime.com/downloads/full-license) | [GPLv3](https://orangedatamining.com/license/) | [GPL](https://waikato.github.io/weka-wiki/faqs/commercial_applications/) | **[MIT](https://docs.dash-ai.com/discover/overview/)** | +| No production paywall | [Partial](https://www.knime.com/knime-business-hub) (a) | [Yes](https://orangedatamining.com/license/) | [Yes](https://waikato.github.io/weka-wiki/faqs/commercial_applications/) | **[Yes](https://docs.dash-ai.com/discover/overview/)** | +| **Extensibility** | | | | | +| Extension language | [Java (official)](https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/) / [Python (Labs)](https://docs.knime.com/latest/pure_python_node_extensions_guide/) | [Python + Qt/PyQt](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html) | [Java](https://waikato.github.io/weka-blog/posts/2018-10-08-making-a-weka-classifier/) | **[Python](https://docs.dash-ai.com/deep-dive/architecture/)** | +| Abstractions by functional role | [1 (generic node)](https://github.com/knime/knime-core/blob/master/org.knime.core/src/eclipse/org/knime/core/node/NodeFactory.java) | [1 (generic widget)](https://orange3.readthedocs.io/projects/orange-development/en/latest/widget.html) | [6 Java hierarchies](https://github.com/Waikato/weka-3.8/tree/master) | **[12 base classes](https://docs.dash-ai.com/deep-dive/components/)** | +| Interface type | [Desktop (Eclipse)](https://www.knime.com/open-source-story) | [Desktop (PyQt)](https://github.com/biolab/orange3) | [Desktop (Java Swing)](https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html) | **[Web (React + FastAPI)](https://docs.dash-ai.com/deep-dive/architecture/)** | +| **Native Catalog** | | | | | +| Tabular classification models | ~11 | ~12 | ~39 | 15 | +| Regression models | ~9 | ~12 | ~32 | 15 | +| Text classification models | Partial (b) | Partial (b) | Partial (b) | 15 | +| Translation models | Partial | No | No | 9 | +| LLMs run locally | Partial (c) | No | No | 5 | +| Image generation models | Partial | No | No | 11 | +| Integrated HPO frameworks | 1 (d) | Partial (d) | 1 (d) | 2 | +| Multilingual interface | No | Partial (e) | No | Yes | + +**Legend:** Yes = full native support, Partial = partial or requires additional extensions, No = not supported + +**(a)** The KNIME Analytics Platform desktop client is fully usable in production for individual/team use at no cost; the paywall applies only to server-side enterprise deployment (KNIME Business Hub). + +**(b)** Reflects platforms that support text classification through text preprocessing/vectorization pipelines combined with general-purpose classifiers rather than dedicated text-classification model catalogs. KNIME provides text processing through its Text Processing extension, Orange through the Orange3-Text add-on, and WEKA through the native `StringToWordVector` filter. WEKA additionally supports neural text-classification models (e.g., CNNs and LSTMs) through the WekaDeepLearning4j package, available as an extension rather than part of the default installation. + +**(c)** Available through KNIME's official, free AI Extension (GPT4All/Ollama), but as user-supplied models via connector nodes rather than a bundled catalog. + +**(d)** Counts reflect each platform's default no-code mechanism; package-manager-installable options (Auto-WEKA, MultiSearch, KNIME Optimization extension) are additional installs. + +**(e)** Slovenian only, via the Trubar localization tool. + +## Sources + +**Licensing** +- KNIME license (GPLv3 + node-API exception): https://www.knime.com/downloads/full-license +- KNIME open source story (license context, Eclipse base): https://www.knime.com/open-source-story +- KNIME Business Hub (commercial-only features: scheduling, governance, RBAC): https://www.knime.com/knime-business-hub +- KNIME AI Extension Guide (confirms the AI Extension ships with the free Analytics Platform, not Business Hub): https://docs.knime.com/latest/analytics_platform_ai_extension_guide/ +- Orange license: https://orangedatamining.com/license/ +- Orange3 GitHub repository (GPLv3+, PyQt dependency): https://github.com/biolab/orange3 +- WEKA licensing FAQ (GPL 2.0 for 3.6, GPL 3.0 for >3.7.5): https://waikato.github.io/weka-wiki/faqs/commercial_applications/ +- dashAI license (MIT): https://docs.dash-ai.com/discover/overview/ + +**Local/API LLM Access** +- KNIME AI Extension Guide (provider list, GPT4All/Ollama connectivity): https://docs.knime.com/latest/analytics_platform_ai_extension_guide/ +- KNIME: "How to leverage open source LLMs locally via Ollama": https://www.knime.com/blog/how-to-leverage-open-source-llms-ollama +- KNIME Community Hub: Local GPT4All LLM Selector node: https://hub.knime.com/knime/extensions/org.knime.python.features.llm/latest/org.knime.python3.nodes.extension.ExtensionNodeSetFactory%24DynamicExtensionNodeFactory:8f5a8be6 +- KNIME blog: "Local LLMs made easy: GPT4All & KNIME Analytics Platform 5.3": https://www.knime.com/blog/local-llms-made-easy + +**Hyperparameter Optimization** + +- WEKA Wiki: Optimizing Parameters (CVParameterSelection, GridSearch, MultiSearch, Auto-WEKA availability): https://waikato.github.io/weka-wiki/optimizing_parameters/ +- Auto-WEKA 2.0 (JMLR paper): https://www.jmlr.org/papers/v18/16-261.html +- KNIME Community Hub: Parameter Optimization Loop Start node (search strategies): https://hub.knime.com/knime/extensions/org.knime.features.optimization/latest/org.knime.optimization.internal.node.parameter.loopstart.LoopStartParOptNodeFactory +- Orange: Parameter Fitter widget documentation (single-integer, Random Forest/PLS only): https://orange3.readthedocs.io/projects/orange-visual-programming/en/master/widgets/evaluate/parameterfitter.html + +**Multilingual Interface** + +- Orange blog: "Meet Trubar, a friend of Orange" (f-string localization tool): https://orangedatamining.com/blog/2023/2023-01-17-trubar/ +- Orange: orange-translations GitHub repository (Slovenian translation files): https://github.com/biolab/orange-translations +- Orange FAQ (confirms Slovenian as the currently shipped translation): https://orangedatamining.com/faq/ + +**Extensibility** +- KNIME: Create a New KNIME Extension (Quickstart Guide, Java path: NodeFactory/NodeModel/NodeDialog/NodeView, plugin.xml, MANIFEST.MF): https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/ +- KNIME: Pure Python Node Extensions Guide (Labs path, knime.yml, pixi): https://docs.knime.com/latest/pure_python_node_extensions_guide/ +- KNIME NodeFactory source (the single generic node abstraction): https://github.com/knime/knime-core/blob/master/org.knime.core/src/eclipse/org/knime/core/node/NodeFactory.java +- Orange: Widget Development, Getting Started: https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html +- Orange: Tutorial (manual GUI construction with `gui.spin`, `gui.checkBox`, etc.): https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial-settings.html +- Orange: OWWidget reference (the single generic widget abstraction): https://orange3.readthedocs.io/projects/orange-development/en/latest/widget.html +- WEKA: "Making a Weka classifier" (official WEKA blog: AbstractClassifier walkthrough, and the `GenericObjectEditor`/`@OptionMetadata` mechanism that partially autogenerates the property-sheet UI from annotations): https://waikato.github.io/weka-blog/posts/2018-10-08-making-a-weka-classifier/ +- WEKA: AbstractClassifier Javadoc (class and method contracts for `buildClassifier(Instances)` and `distributionForInstance(Instance)`): https://weka.sourceforge.io/doc.dev/weka/classifiers/AbstractClassifier.html +- WEKA: Filter Javadoc (base abstraction for the filter family): https://weka.sourceforge.io/doc.dev/weka/filters/Filter.html +- WEKA: six top-level abstract hierarchies confirmed against the repository structure (`weka.classifiers.AbstractClassifier`, `weka.clusterers.AbstractClusterer`, `weka.associations.AbstractAssociator`, `weka.filters.Filter`, `weka.attributeSelection.ASEvaluation`, `weka.core.converters.AbstractLoader`): https://github.com/Waikato/weka-3.8/tree/master +- dashAI: Abstract Classes API reference (12 base classes): https://docs.dash-ai.com/deep-dive/components/ +- dashAI: Architecture deep dive (FastAPI/React, Pydantic-to-JSON-Schema autogeneration): https://docs.dash-ai.com/deep-dive/architecture/ + +**Interface Architecture** +- KNIME on Eclipse: https://www.knime.com/open-source-story +- Orange on PyQt: https://github.com/biolab/orange3 +- WEKA Explorer extends `javax.swing.JPanel` (Javadoc): https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html +- dashAI client-server architecture: https://docs.dash-ai.com/deep-dive/architecture/ + +**Native Catalog** +Catalog counts were obtained by direct inspection of each platform's own interface (KNIME's node repository, Orange's widget catalog, WEKA's Explorer / Package Manager, and dashAI's model catalog), rather than from a single external listing page, since none of the four platforms publishes an authoritative count broken down exactly this way. diff --git a/docs/docs/deep-dive/components.md b/docs/docs/deep-dive/components.md index e4d2989be..fd97d3b0f 100644 --- a/docs/docs/deep-dive/components.md +++ b/docs/docs/deep-dive/components.md @@ -21,7 +21,8 @@ Each component class declares a `TYPE` class attribute that determines its categ | `GenerativeTask` | `BaseGenerativeTask` | Define generative task semantics | TextToTextGenerationTask, TextToImageGenerationTask, ControlNetTask | | `Metric` | `BaseMetric` | Evaluate model performance | Accuracy, F1, RMSE, MAE | | `Explorer` | `BaseExplorer` | Visualize and analyze data | ScatterPlotExplorer, HistogramPlotExplorer | -| `Explainer` | `BaseExplainer` | Interpret model predictions | KernelShap, PermutationFeatureImportance | +| `GlobalExplainer` | `BaseGlobalExplainer` | Interpret overall model behavior | PermutationFeatureImportance, PartialDependence | +| `LocalExplainer` | `BaseLocalExplainer` | Interpret individual predictions | KernelShap | | `Converter` | `BaseConverter` | Transform features | StandardScaler, OneHotEncoder, PCA, SMOTE | | `DataLoader` | `BaseDataLoader` | Load datasets from files | CSVDataLoader, ExcelDataLoader | | `Optimizer` | `BaseOptimizer` | Hyperparameter optimization | Optuna based optimizers | diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md index 4eea6ff2b..d74b748d3 100644 --- a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md +++ b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md @@ -8,7 +8,7 @@ sidebar_position: 2 ## Requisitos Previos -- Python 3.10 a 3.13 +- [uv](https://docs.astral.sh/uv/getting-started/installation/) (administra Python y las dependencias; Python 3.10 a 3.13) - Node.js (LTS) y Yarn 3.5.0 - Git @@ -22,22 +22,51 @@ git checkout develop ## 2. Configuración del Backend -Crea y activa un entorno de Python (conda o venv): +Instala todas las dependencias (uv crea el `.venv` e instala el paquete +en modo editable, incluyendo las dependencias de desarrollo): ```bash -conda create -n dashai python=3.10 -conda activate dashai +uv sync +uv run pre-commit install ``` -Instala el paquete en modo editable con las dependencias de desarrollo: +En máquinas sin GPU NVIDIA puedes usar los wheels de PyTorch solo-CPU, +que son mucho más livianos: ```bash -pip install -r requirements.txt -pip install -e . -pip install -r requirements-dev.txt +uv sync --extra cpu +``` + +En máquinas con NVIDIA, el extra `cuda` fija los wheels de PyTorch CUDA 12.8. +Para tener además soporte LLM (GGUF) con offload a CUDA, define `CMAKE_ARGS` +para que `llama-cpp-python` compile contra CUDA (requiere CMake, un compilador +C y el toolkit de CUDA): + +```bash +uv cache clean llama-cpp-python +CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python +``` + +El primer comando y el flag `--reinstall-package` importan: uv omite paquetes +que ya están instalados y cachea los wheels compilados, y ninguna de esas dos +verificaciones mira `CMAKE_ARGS`, así que sin ellos se reutiliza en silencio +un build CPU anterior. Sin `CMAKE_ARGS`, `llama-cpp-python` se instala igual +pero corre en CPU (no existe wheel CUDA precompilado en PyPI). Si nvcc rechaza +tu gcc por ser muy nuevo, apúntalo a uno más antiguo que tengas instalado, por +ejemplo `CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"`. + +Como alternativa, `pip` a secas funciona dentro de cualquier entorno (venv o +conda), ya que toda la metadata vive en `pyproject.toml`. Ojo que esto no usa +el lockfile, así que las versiones pueden diferir levemente de las que usan el +equipo y el CI: + +```bash +pip install -e . --group dev # --group requiere pip >= 25.1 pre-commit install ``` +Si usas la vía pip, omite el prefijo `uv run` en los comandos siguientes. + ## 3. Configuración del Frontend ```bash @@ -50,11 +79,16 @@ yarn install **Backend** (desde la raíz del repositorio): ```bash -python -m DashAI +uv run python -m DashAI # o -dashai --no-browser --logging-level INFO +uv run dashai --no-browser --logging-level INFO ``` +**Importante:** si sincronizaste con un extra, pásale el mismo extra a +`uv run` (por ejemplo `uv run --extra cpu python -m DashAI`). Un `uv run` sin +flags re-sincroniza el entorno al set por defecto y revierte tu build de +PyTorch al de PyPI. + **Frontend** (servidor de desarrollo con recarga en caliente): ```bash @@ -69,8 +103,8 @@ El backend corre en `http://localhost:8000` y el servidor de desarrollo del fron **Python** (usando Ruff): ```bash -ruff check . --fix -ruff format . +uv run ruff check . --fix +uv run ruff format . ``` **Frontend** (ESLint + Prettier): @@ -86,10 +120,10 @@ dashAI usa hooks de pre-commit para mantener la calidad del código: ```bash # Ejecutar todos los hooks manualmente -pre-commit run --all-files +uv run pre-commit run --all-files # Ejecutar sobre archivos en staging (ocurre automáticamente en git commit) -pre-commit run +uv run pre-commit run ``` ## Estructura del Proyecto diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/benchmark.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/benchmark.md deleted file mode 100644 index f43f9526e..000000000 --- a/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/benchmark.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: Benchmark comparativo -description: Comparación de dashAI con KNIME, Orange y WEKA en licencia, extensibilidad y cobertura de tareas. -sidebar_label: Benchmark comparativo ---- - -# Benchmark comparativo - -Esta sección compara dashAI con KNIME Analytics Platform, Orange Data Mining y WEKA, las principales plataformas no code ML open source con arquitectura comparable. - ---- - -## Metodología - -El subconjunto de plataformas se delimitó a herramientas que cumplen simultáneamente tres condiciones: distribución open source, operación sin código para el usuario final y arquitectura de catálogo extensible por terceros. KNIME Analytics Platform, Orange Data Mining y WEKA satisfacen ese criterio junto a dashAI. - -La comparación se estructura en tres dimensiones medibles: condiciones de licencia, costo arquitectónico de extensión y cobertura paradigmática del catálogo nativo. Para la dimensión de extensibilidad, el boilerplate reportado corresponde a componentes de referencia implementados en cada plataforma a partir de sus repositorios oficiales; las fuentes están listadas al final de esta página. - ---- - -## Licencia - -dashAI se distribuye bajo licencia **MIT**, lo que permite su uso, modificación y distribución sin restricciones en proyectos comerciales o institucionales. - -KNIME y Orange utilizan GPLv3; WEKA, GPL. Las tres licencias incluyen copyleft sobre obras derivadas distribuidas. KNIME presenta además funcionalidades adicionales (scheduling, despliegue gobernado, RBAC y la AI Extension) disponibles únicamente a través de KNIME Business Hub bajo licenciamiento comercial. - ---- - -## Extensibilidad - -dashAI expone doce clases base organizadas por rol funcional: `BaseModel`, `BaseMetric`, `BaseTask`, `BaseExplainer`, entre otras. Un nuevo componente se implementa subclasificando la abstracción correspondiente y declarando sus parámetros mediante un schema Pydantic. A partir del schema, la plataforma genera automáticamente el formulario de configuración en la interfaz, sin requerir código de frontend. El componente se distribuye vía PyPI e instala directamente desde la interfaz de dashAI. - -El boilerplate resultante es de aproximadamente **20-40 líneas de código** para componentes simples (métricas, clasificadores básicos) y **70-110 líneas** para modelos con múltiples hiperparámetros, donde la mayor parte del código corresponde a la declaración del schema de parámetros. - -Para referencia, los mecanismos de extensión de las otras plataformas evaluadas son los siguientes: - -- **Orange** permite extensiones en Python, pero cada widget requiere acoplarse a Qt/PyQt e instanciar manualmente los controles de interfaz (≈50-70 líneas de código). -- **WEKA** se extiende en Java heredando de `AbstractClassifier` e implementando `buildClassifier(Instances)` y `distributionForInstance(Instance)` sobre la abstracción `Instances` (≈80-120 líneas de código). -- **KNIME**, en su vía oficial, requiere un plugin OSGi/Eclipse con cuatro clases Java (`NodeFactory`, `NodeModel`, `NodeDialog` y `NodeView`), además de descriptores `plugin.xml` y `MANIFEST.MF`, y build con Maven/Tycho (≈150-250 líneas de código). Desde la versión 4.6 existe una vía experimental en Python (Labs) que genera UI desde declaraciones de parámetros, pero no reemplaza al camino Java como vía oficial y requiere herramientas de empaquetado propias (`pixi`, `knime.yml`). - -### Arquitectura de interfaz - -dashAI adopta una arquitectura cliente servidor: un servidor FastAPI expone el catálogo de componentes, datasets, jobs de entrenamiento y resultados; un frontend React consume esa API vía HTTP. Cuando se registra un componente nuevo, el servidor expone su schema JSON y el frontend renderiza el formulario de configuración sin conocimiento previo del componente. El servidor puede ejecutarse en cualquier máquina y accederse desde un navegador, incluso desde otro dispositivo en la misma red local. - -KNIME está construido sobre Eclipse RCP, Orange sobre PyQt y WEKA sobre Java Swing. En los tres casos la interfaz y la lógica de negocio corren en el mismo proceso; añadir un componente nuevo implica modificar también la capa de presentación. - ---- - -## Cobertura de tareas - -El catálogo nativo de dashAI cubre cuatro tareas predictivas (clasificación tabular, regresión, clasificación de texto y traducción) con aproximadamente quince modelos cada una, junto con cinco LLMs y once modelos de generación de imagen, todos ejecutables localmente. La optimización de hiperparámetros integra Optuna y HyperOpt. - -La interfaz está disponible en español e inglés. - ---- - -## Tabla comparativa - -| Criterio | KNIME | Orange | WEKA | **dashAI** | -| ------------------------------- | :----------------------------: | :-----------------: | :------------------: | :-----------------------: | -| **Licenciamiento** | | | | | -| Licencia | GPLv3 | GPLv3 | GPL | **MIT** | -| Sín paywall productivo | No | Sí | Sí | **Sí** | -| **Extensibilidad** | | | | | -| Lenguaje de extensión | Java (oficial) / Python (Labs) | Python + Qt/PyQt | Java | **Python** | -| Abstracciones por rol funcional | 1 (nodo genérico) | 1 (widget genérico) | 6 jerarquías Java | **12 clases base** | -| Tipo de interfaz | Desktop (Eclipse) | Desktop (PyQt) | Desktop (Java Swing) | **Web (React + FastAPI)** | -| UI generada automáticamente | Parcial | No | No | **Sí** | -| Boilerplate por componente | 150-250 LdC (Java) | 50-70 LdC | 80-120 LdC | **20-40 LdC** | -| Soporte GPU | Parcial | Parcial | Parcial | **Partial** | -| Interfaz multiidioma (ES/EN) | No | No | No | **Sí** | -| **Catálogo nativo** | | | | | -| Modelos clasificación tabular | ~11 | ~12 | ~39 | **15** | -| Modelos de regresión | ~9 | ~12 | ~32 | **15** | -| Modelos clasificación de texto | Parcial | Parcial | 33 (BoW) | **15** | -| Modelos de traducción | Parcial | No | No | **9** | -| LLMs ejecutados localmente | No | No | No | **5** | -| Modelos de generación de imagen | Parcial | No | No | **11** | -| Frameworks de HPO integrados | 1 | 0 | 1 | **2** | - -**Convenciones:** Sí = soporte nativo completo, Parcial = parcial o requiere extensiones adicionales, No = no soportado, LdC = líneas de código. - -:::note UI generada automáticamente -La vía oficial de KNIME (Java) requiere programar `NodeDialog` y `NodeView` manualmente. La vía experimental Python (Labs, desde v4.6) genera UI desde declaraciones de parámetros, pero no es el camino oficial. Orange y WEKA requieren código de interfaz manual en todos los casos. -::: - -:::note Boilerplate por componente -Las 150-250 LdC de KNIME corresponden a la vía Java oficial. La vía Python (Labs) reduce ese número pero añade archivos de configuración propios (`knime.yml`, `pixi.toml`). En dashAI: ≈20-40 LdC para componentes simples; ≈70-110 LdC para modelos complejos con múltiples hiperparámetros, donde la mayor parte del código es la declaración del schema de parámetros. -::: - -:::note Solapamiento entre tasks (dashAI) -9 de los 15 modelos de clasificación tabular tienen contraparte en regresión (Random Forest, Gradient Boosting, SVM, entre otros); cada task dispone de su propia implementación y configuración independiente. En WEKA, regresión y clasificación heredan de `AbstractClassifier` y se diferencian por capability flag. -::: - ---- - -## Fuentes - -- [knime-python-extension-template: `extension.py`](https://github.com/knime-oss/knime-python-extension-template/blob/releases/2025-12/src/extension.py) -- [Orange3: `OWDataSamplerA.py`](https://github.com/biolab/orange3/blob/master/doc/development/source/orange-demo/orangedemo/OWDataSamplerA.py) -- [knime-examples: `UnitConverterNodeModel.java`](https://github.com/knime-oss/knime-examples/blob/master/org.knime.examples.unitconverter/src/org/knime/examples/unitconverter/UnitConverterNodeModel.java) -- [weka-3.8: `NBTree.java`](https://github.com/Waikato/weka-3.8/blob/master/packages/internal/naiveBayesTree/src/main/java/weka/classifiers/trees/NBTree.java) -- [weka-3.8: `SMOTE.java`](https://github.com/Waikato/weka-3.8/blob/master/packages/external/SMOTE/src/main/java/weka/filters/supervised/instance/SMOTE.java) diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/comparison.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/comparison.md new file mode 100644 index 000000000..a2e3281ba --- /dev/null +++ b/docs/i18n/es/docusaurus-plugin-content-docs/current/deep-dive/comparison.md @@ -0,0 +1,114 @@ +--- +title: Benchmark comparativo +description: Comparación de dashAI con KNIME, Orange y WEKA en licencia, extensibilidad y cobertura de tareas. +sidebar_label: Benchmark comparativo +--- + +# Benchmark comparativo + +Esta sección compara dashAI con KNIME Analytics Platform, Orange Data Mining y WEKA, tres plataformas de machine learning de código abierto y sin código que cumplen los criterios de selección descritos en la sección de Metodología a continuación. + +--- + +## Metodología + +El conjunto de plataformas consideradas se restringió a herramientas que satisfacen conjuntamente tres condiciones: distribución de código abierto, operación sin código para el usuario final, y una arquitectura de catálogo que puede ser extendida por terceros. KNIME Analytics Platform, Orange Data Mining y WEKA cumplen estas condiciones junto con dashAI. + +La comparación se organiza en torno a tres dimensiones medibles: los términos de licencia, el mecanismo de extensión, y la cobertura de paradigmas de tareas del catálogo nativo. Para las dimensiones de licencia y extensibilidad, cada afirmación a continuación está vinculada directamente a su fuente primaria (texto oficial de la licencia, documentación oficial para desarrolladores, o el archivo fuente correspondiente), ya sea en línea o en la sección de [Fuentes](#fuentes). Para la dimensión de cobertura de tareas, los conteos de catálogo se obtuvieron mediante inspección directa de la propia interfaz de cada plataforma (repositorio de nodos, catálogo de widgets, gestor de paquetes, o catálogo de modelos) en lugar de a partir de un único listado externo, ya que ninguna de las cuatro plataformas publica un conteo canónico desglosado de esta manera; consulte [Fuentes](#fuentes) para más detalle. + +--- + +## Licencia + +dashAI se distribuye bajo la **[licencia MIT](https://docs.dash-ai.com/discover/overview/)**, que permite el uso, la modificación y la redistribución —incluso en entornos comerciales o institucionales— sujeto a la conservación del aviso de copyright y licencia original. + +[KNIME](https://www.knime.com/downloads/full-license) y [Orange](https://orangedatamining.com/license/) se distribuyen bajo GPLv3; [WEKA](https://waikato.github.io/weka-wiki/faqs/commercial_applications/), bajo GPL. Las tres licencias imponen obligaciones de copyleft sobre los trabajos derivados distribuidos. KNIME además ofrece funcionalidades no incluidas en su distribución de código abierto —programación de tareas, despliegue gobernado, control de acceso basado en roles, y la AI Extension— disponibles únicamente a través de [KNIME Business Hub](https://www.knime.com/knime-business-hub) bajo una licencia comercial. + +--- + +## Extensibilidad + +dashAI expone [doce clases base](https://docs.dash-ai.com/api/back.html), organizadas por rol funcional, incluyendo `BaseModel`, `BaseMetric`, `BaseTask` y `BaseExplainer`. Un nuevo componente se implementa creando una subclase de la clase base correspondiente y declarando sus parámetros mediante un esquema de Pydantic; [la plataforma deriva el formulario de configuración que se muestra en la interfaz directamente de este esquema](https://docs.dash-ai.com/deep-dive/architecture/), sin código adicional de frontend. El componente resultante se distribuye vía PyPI y se instala desde la propia interfaz de dashAI. + +Los mecanismos de extensión de las otras tres plataformas son los siguientes: + +- **Orange** permite [extensiones en Python](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html), pero cada widget debe acoplarse a Qt/PyQt e [instanciar manualmente los controles de la interfaz](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial-settings.html), ya que no existe generación automática de formularios a partir de esquemas. +- **WEKA** sigue una arquitectura de plugins organizada en familias principales de componentes, incluidas clasificadores, agrupadores, asociadores, filtros, métodos de selección de atributos y cargadores de datos. Cada familia expone su propia API de extensión mediante clases base abstractas y/o interfaces (por ejemplo, `AbstractClassifier` para clasificadores y `Filter` para filtros). Los nuevos clasificadores suelen implementarse heredando de `AbstractClassifier` y redefiniendo los métodos `buildClassifier(Instances)` y `distributionForInstance(Instance)`, que operan sobre la abstracción de datos `Instances` de WEKA. La [estructura del repositorio de WEKA](https://github.com/Waikato/weka-3.8/tree/master), la [Javadoc de AbstractClassifier](https://weka.sourceforge.io/doc.dev/weka/classifiers/AbstractClassifier.html) y la [Javadoc de Filter](https://weka.sourceforge.io/doc.dev/weka/filters/Filter.html) documentan estos puntos de extensión. +- **KNIME**, por la vía oficial, requiere un [plugin OSGi/Eclipse con cuatro clases Java](https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/) (`NodeFactory`, `NodeModel`, `NodeDialog` y `NodeView`), además de descriptores `plugin.xml` y `MANIFEST.MF`, y una compilación Maven/Tycho. Desde la versión 4.6 existe una [vía experimental en Python (Labs)](https://docs.knime.com/latest/pure_python_node_extensions_guide/) que genera un diálogo a partir de declaraciones de parámetros, pero no reemplaza la vía Java como enfoque oficial y requiere sus propias herramientas de empaquetado (`pixi`, `knime.yml`). + +### Arquitectura de la interfaz + +dashAI adopta una [arquitectura cliente-servidor](https://docs.dash-ai.com/deep-dive/architecture/): un servidor FastAPI expone el catálogo de componentes, los datasets, los trabajos de entrenamiento y los resultados; un frontend en React consume esa API vía HTTP. Cuando se registra un nuevo componente, el servidor expone su esquema JSON y el frontend renderiza el formulario de configuración sin conocimiento previo del componente. El servidor puede ejecutarse en cualquier máquina y ser accedido desde un navegador, incluso desde otro dispositivo en la misma red local. + +[KNIME está construido sobre Eclipse RCP](https://www.knime.com/open-source-story), [Orange sobre PyQt](https://github.com/biolab/orange3), y [WEKA sobre Java Swing](https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html). En los tres casos la interfaz y la lógica de negocio se ejecutan en el mismo proceso; agregar un nuevo componente también requiere modificar la capa de presentación. + +--- + +## Cobertura de tareas + +El catálogo nativo de dashAI cubre cuatro tareas predictivas: clasificación tabular, regresión y clasificación de texto, cada una con quince modelos, y traducción, con nueve. Además incluye cinco modelos de lenguaje de gran tamaño (LLMs) y once modelos de generación de imágenes. Todos los modelos del catálogo nativo se ejecutan localmente. La optimización de hiperparámetros se soporta mediante dos frameworks integrados, Optuna y HyperOpt. + +La interfaz está disponible en español, inglés, chino, alemán y portugués. + +--- + +## Tabla comparativa + +| Criterio | KNIME | Orange | WEKA | **dashAI** | +| ---------------------------------- | :-------------------------------: | :-------------------: | :----------------------: | :---------------------------: | +| **Licencia** | | | | | +| Licencia | [GPLv3](https://www.knime.com/downloads/full-license) | [GPLv3](https://orangedatamining.com/license/) | [GPL](https://waikato.github.io/weka-wiki/faqs/commercial_applications/) | **[MIT](https://docs.dash-ai.com/discover/overview/)** | +| Sin muro de pago en producción | [No](https://www.knime.com/knime-business-hub) | [Sí](https://orangedatamining.com/license/) | [Sí](https://waikato.github.io/weka-wiki/faqs/commercial_applications/) | **[Sí](https://docs.dash-ai.com/discover/overview/)** | +| **Extensibilidad** | | | | | +| Lenguaje de extensión | [Java (oficial)](https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/) / [Python (Labs)](https://docs.knime.com/latest/pure_python_node_extensions_guide/) | [Python + Qt/PyQt](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html) | [Java](https://waikato.github.io/weka-blog/posts/2018-10-08-making-a-weka-classifier/) | **[Python](https://docs.dash-ai.com/deep-dive/architecture/)** | +| Abstracciones por rol funcional | [1 (nodo genérico)](https://github.com/knime/knime-core/blob/master/org.knime.core/src/eclipse/org/knime/core/node/NodeFactory.java) | [1 (widget genérico)](https://orange3.readthedocs.io/projects/orange-development/en/latest/widget.html) | [6 jerarquías Java](https://github.com/Waikato/weka-3.8/tree/master) | **[12 clases base](https://docs.dash-ai.com/api/back.html)** | +| Tipo de interfaz | [Escritorio (Eclipse)](https://www.knime.com/open-source-story) | [Escritorio (PyQt)](https://github.com/biolab/orange3) | [Escritorio (Java Swing)](https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html) | **[Web (React + FastAPI)](https://docs.dash-ai.com/deep-dive/architecture/)** | +| UI autogenerada | [Parcial](https://docs.knime.com/latest/pure_python_node_extensions_guide/) | [No](https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial-settings.html) | [Parcial](https://waikato.github.io/weka-blog/posts/2018-10-08-making-a-weka-classifier/) | **[Sí](https://docs.dash-ai.com/deep-dive/architecture/)** | +| Soporte GPU | Parcial | Parcial | Parcial | **Parcial** | +| Interfaz multilingüe | No | No | No | **Sí** | +| **Catálogo nativo** | | | | | +| Modelos de clasificación tabular | ~11 | ~12 | ~39 | 15 | +| Modelos de regresión | ~9 | ~12 | ~32 | 15 | +| Modelos de clasificación de texto | Parcial | Parcial | Parcial | 15 | +| Modelos de traducción | Parcial | No | No | 9 | +| LLMs ejecutados localmente | No | No | No | 5 | +| Modelos de generación de imágenes | Parcial | No | No | 11 | +| Frameworks de HPO integrados | 1 | 0 | 1 | 2 | + +**Leyenda:** Sí = soporte nativo completo, Parcial = soporte parcial o requiere extensiones adicionales, No = no soportado + +--- + +## Fuentes + +**Licencia** +- Licencia de KNIME (GPLv3 + excepción de node-API): https://www.knime.com/downloads/full-license +- Historia de código abierto de KNIME (contexto de licencia, base Eclipse): https://www.knime.com/open-source-story +- KNIME Business Hub (funcionalidades solo comerciales: programación, gobernanza, RBAC, AI Gateway): https://www.knime.com/knime-business-hub +- Licencia de Orange: https://orangedatamining.com/license/ +- Repositorio de Orange3 en GitHub (GPLv3+, dependencia de PyQt): https://github.com/biolab/orange3 +- FAQ de licenciamiento de WEKA (GPL 2.0 para 3.6, GPL 3.0 para >3.7.5): https://waikato.github.io/weka-wiki/faqs/commercial_applications/ +- Licencia de dashAI (MIT): https://docs.dash-ai.com/discover/overview/ + +**Extensibilidad** +- KNIME: Create a New KNIME Extension (Quickstart Guide, vía Java: NodeFactory/NodeModel/NodeDialog/NodeView, plugin.xml, MANIFEST.MF): https://docs.knime.com/latest/analytics_platform_new_node_quickstart_guide/ +- KNIME: Pure Python Node Extensions Guide (vía Labs, knime.yml, pixi): https://docs.knime.com/latest/pure_python_node_extensions_guide/ +- Código fuente de NodeFactory de KNIME (la única abstracción genérica de nodo): https://github.com/knime/knime-core/blob/master/org.knime.core/src/eclipse/org/knime/core/node/NodeFactory.java +- Orange: Widget Development, Getting Started: https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial.html +- Orange: Tutorial (construcción manual de la GUI con `gui.spin`, `gui.checkBox`, etc.): https://orange3.readthedocs.io/projects/orange-development/en/latest/tutorial-settings.html +- Orange: referencia de OWWidget (la única abstracción genérica de widget): https://orange3.readthedocs.io/projects/orange-development/en/latest/widget.html +- WEKA: "Making a Weka classifier" (blog oficial de WEKA: recorrido por AbstractClassifier, y el mecanismo `GenericObjectEditor`/`@OptionMetadata` que autogenera parcialmente la UI de la hoja de propiedades a partir de anotaciones): https://waikato.github.io/weka-blog/posts/2018-10-08-making-a-weka-classifier/ +- WEKA: Javadoc de AbstractClassifier (clase y contratos de los métodos `buildClassifier(Instances)` y `distributionForInstance(Instance)`): https://weka.sourceforge.io/doc.dev/weka/classifiers/AbstractClassifier.html +- WEKA: Javadoc de Filter (abstracción base para la familia de filtros): https://weka.sourceforge.io/doc.dev/weka/filters/Filter.html +- WEKA: seis jerarquías abstractas de nivel superior confirmadas contra la estructura del repositorio (`weka.classifiers.AbstractClassifier`, `weka.clusterers.AbstractClusterer`, `weka.associations.AbstractAssociator`, `weka.filters.Filter`, `weka.attributeSelection.ASEvaluation`, `weka.core.converters.AbstractLoader`): https://github.com/Waikato/weka-3.8/tree/master +- dashAI: referencia de API de clases abstractas (12 clases base): https://docs.dash-ai.com/api/back.html +- dashAI: profundización en arquitectura (FastAPI/React, autogeneración de Pydantic a JSON Schema): https://docs.dash-ai.com/deep-dive/architecture/ + +**Arquitectura de la interfaz** +- KNIME sobre Eclipse: https://www.knime.com/open-source-story +- Orange sobre PyQt: https://github.com/biolab/orange3 +- WEKA Explorer extiende `javax.swing.JPanel` (Javadoc): https://weka.sourceforge.io/doc.stable/weka/gui/explorer/Explorer.html +- Arquitectura cliente-servidor de dashAI: https://docs.dash-ai.com/deep-dive/architecture/ + +**Catálogo nativo** +Los conteos del catálogo (modelos por tarea, soporte GPU, disponibilidad multilingüe, frameworks de HPO) se obtuvieron mediante inspección directa de la propia interfaz de cada plataforma (repositorio de nodos de KNIME, catálogo de widgets de Orange, Explorer/Package Manager de WEKA, y catálogo de modelos de dashAI), en lugar de a partir de un único listado externo, ya que ninguna de las cuatro plataformas publica un conteo autoritativo desglosado exactamente de esta manera. diff --git a/docs/sidebars.js b/docs/sidebars.js index b38ae7e43..5b51548fd 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -62,7 +62,7 @@ const sidebars = { "deep-dive/dashai-dataset", ], }, - "deep-dive/benchmark", + "deep-dive/comparison", "deep-dive/replicability", ], diff --git a/docs/static/img/institutions/fcfm-logo.png b/docs/static/img/institutions/fcfm-logo.png new file mode 100644 index 000000000..260cc3ce3 Binary files /dev/null and b/docs/static/img/institutions/fcfm-logo.png differ diff --git a/docs/static/institutions/institutions.json b/docs/static/institutions/institutions.json index 74822d113..01a268cdb 100644 --- a/docs/static/institutions/institutions.json +++ b/docs/static/institutions/institutions.json @@ -5,16 +5,42 @@ "id": "uchile", "name": "University of Chile", "fullName": "Universidad de Chile", - "role": "Leading Institution", + "role": { + "en": "Leading Institution", + "es": "Institución Líder", + "pt": "Instituição Líder", + "de": "Federführende Institution", + "zh": "牵头机构" + }, "url": "https://uchile.cl/", "logo": "img/institutions/dcc-logo.png", "small": true }, + { + "id": "fcfm", + "name": "Fcfm", + "fullName": "Facultad de Ciencias Físicas y Matemáticas Universidad de Chile", + "role": { + "en": "Leading Institution", + "es": "Institución Líder", + "pt": "Instituição Líder", + "de": "Federführende Institution", + "zh": "牵头机构" + }, + "url": "https://www.fcfm.uchile.cl/", + "logo": "img/institutions/fcfm-logo.png" + }, { "id": "cenia", "name": "CENIA", "fullName": "National Center for Artificial Intelligence", - "role": "Associated Institution", + "role": { + "en": "Associated Institution", + "es": "Institución Asociada", + "pt": "Instituição Associada", + "de": "Assoziierte Institution", + "zh": "联合机构" + }, "url": "https://www.cenia.cl/", "logo": "img/institutions/cenia-logo.png" }, @@ -22,7 +48,13 @@ "id": "imfd", "name": "IMFD", "fullName": "Millennium Institute for Foundational Data Research", - "role": "Collaborator", + "role": { + "en": "Collaborator", + "es": "Colaborador", + "pt": "Colaborador", + "de": "Mitwirkender", + "zh": "合作者" + }, "url": "https://imfd.cl/en/", "logo": "img/institutions/imfd-logo.png" }, @@ -30,7 +62,13 @@ "id": "unholster", "name": "Unholster", "fullName": "Unholster", - "role": "Industry Partner", + "role": { + "en": "Industry Partner", + "es": "Socio Industrial", + "pt": "Parceiro Industrial", + "de": "Industriepartner", + "zh": "行业合作伙伴" + }, "url": "https://unholster.com/", "logo": "img/institutions/unholster-logo.png" } @@ -39,7 +77,9 @@ "text": { "en": "Supported by ANID through Fondef IDEA ID25I10330, Fondef VIU23P 0110, and grants supporting the centers CENIA (FB210017) and IMFD (ICN17_002). Developed by students of DCC UChile and UTFSM.", "es": "Financiado por ANID a través de Fondef IDEA ID25I10330, Fondef VIU23P 0110 y los fondos que apoyan a los centros CENIA (FB210017) e IMFD (ICN17_002). Desarrollado por estudiantes del DCC UChile y la UTFSM.", - "pt": "Financiado pela ANID por meio do Fondef IDEA ID25I10330, do Fondef VIU23P 0110 e dos fundos que apoiam os centros CENIA (FB210017) e IMFD (ICN17_002). Desenvolvido por estudantes do DCC UChile e da UTFSM." + "pt": "Financiado pela ANID por meio do Fondef IDEA ID25I10330, do Fondef VIU23P 0110 e dos fundos que apoiam os centros CENIA (FB210017) e IMFD (ICN17_002). Desenvolvido por estudantes do DCC UChile e da UTFSM.", + "de": "Unterstützt von ANID durch Fondef IDEA ID25I10330, Fondef VIU23P 0110 sowie durch Fördermittel für die Zentren CENIA (FB210017) und IMFD (ICN17_002). Entwickelt von Studierenden der DCC UChile und der UTFSM.", + "zh": "由 ANID 通过 Fondef IDEA ID25I10330、Fondef VIU23P 0110 项目,以及支持 CENIA(FB210017)和 IMFD(ICN17_002)中心的资助提供支持。由 DCC UChile 和 UTFSM 的学生开发。" }, "grants": ["ID25I10330", "VIU23P 0110", "FB210017", "ICN17_002"], "logos": [ diff --git a/images/logos.png b/images/logos.png index c4cd13fd0..43505f0a6 100644 Binary files a/images/logos.png and b/images/logos.png differ diff --git a/pyproject.toml b/pyproject.toml index 2a849accb..e0e43b452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,143 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "dashAI" +version = "0.9.7" +description = "dashAI: a graphical toolbox for training, evaluating and deploying state-of-the-art AI models." +readme = "README.rst" +license = "MIT" +authors = [{ name = "dashAI Team", email = "contacto@dash-ai.com" }] +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", +] +dependencies = [ + "setuptools>=65.0.0,<82", + "fastapi[all]", + "SQLAlchemy", + "streaming_form_data", + "alembic", + "kink", + "numpy", + "pandas<3.0.0", + "joblib", + "pydantic", + "pydantic-settings", + "starlette", + "scikit-learn<1.8.0", + "datasets", + "diffusers", + "evaluate", + "accelerate", + "Pillow", + "beartype", + "plotly", + "shap>=0.48", + "pymc-bart", + # pymc still imports the pre-1.0 arviz API (InferenceData, concat) but its + # metadata sets no upper bound, so the resolver must be capped here + "arviz<1.0.0", + "typer", + "rich", + "torch", + "torchvision", + "transformers", + "controlnet_aux", + "sacrebleu", + "sentencepiece", + "optuna", + "cmaes", + "hyperopt", + "nvidia-ml-py", + "openpyxl", + "httpx", + "wordcloud", + "opencv-python", + "protobuf", + "imblearn", + "numba", + "llvmlite", + "huey", + "ijson", + "greenery==3.2", + "xlrd", + "filetype", + "torchmetrics", + "pywebview", + "openml", + "oslo.concurrency", + "grad-cam>=1.5.5", + "dice-ml>=0.12", + "lime>=0.2.0.1", +] + +[project.optional-dependencies] +# PyTorch CPU wheels + precompiled CPU llama-cpp (no CUDA SDK / drivers needed) +cpu = ["torch", "torchvision", "llama-cpp-python"] +# PyTorch CUDA 12.8 wheels; llama-cpp-python needs CMAKE_ARGS="-DGGML_CUDA=on" +# at install time to compile with CUDA offload (see Dockerfile.cuda) +cuda = ["torch", "torchvision", "llama-cpp-python"] + +[project.urls] +Homepage = "https://github.com/DashAISoftware/DashAI" +Documentation = "https://dash-ai.com/" +Changelog = "https://dash-ai.com/changelog.html" +"Issue Tracker" = "https://github.com/DashAISoftware/DashAI/issues" + +[project.scripts] +dashai = "DashAI.__main__:run" +DashAI = "DashAI.__main__:run" + +[dependency-groups] +dev = [ + "pre-commit", + "ruff", + "sphinx_rtd_theme", + "sphinx", + "sqlalchemy-stubs", + "pytest", + "pytest-cov", + "pytest-asyncio", +] + +[tool.setuptools.packages.find] +include = ["DashAI*"] + +[tool.uv] +conflicts = [[{ extra = "cpu" }, { extra = "cuda" }]] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "cuda" }, +] +torchvision = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "cuda" }, +] +llama-cpp-python = [{ index = "llama-cpp-cpu", extra = "cpu" }] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu128" +url = "https://download.pytorch.org/whl/cu128" +explicit = true + +[[tool.uv.index]] +name = "llama-cpp-cpu" +url = "https://abetlen.github.io/llama-cpp-python/whl/cpu" +explicit = true + [tool.ruff] line-length = 88 lint.select = [ diff --git a/requirements-cpu.txt b/requirements-cpu.txt deleted file mode 100644 index ca9663e25..000000000 --- a/requirements-cpu.txt +++ /dev/null @@ -1,16 +0,0 @@ -# =================================================================== -# DashAI CPU-only extras (PyTorch CPU wheels + CPU llama-cpp) -# =================================================================== -# Common deps live in requirements.txt; this file only adds the -# packages that need a special index/build for CPU-only hosts. -# Install: pip install -r requirements-cpu.txt -# =================================================================== - --r requirements.txt - -# PyTorch CPU wheels (no CUDA SDK / drivers needed) -torch --index-url https://download.pytorch.org/whl/cpu -torchvision --index-url https://download.pytorch.org/whl/cpu - -# Llama CPU-only (no CUDA) -llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu diff --git a/requirements-cuda.txt b/requirements-cuda.txt deleted file mode 100644 index d1bc6cf9f..000000000 --- a/requirements-cuda.txt +++ /dev/null @@ -1,17 +0,0 @@ -# =================================================================== -# DashAI CUDA extras (CUDA 12.8 PyTorch wheels + CUDA llama-cpp) -# =================================================================== -# Common deps live in requirements.txt; this file only adds the -# packages that need a special index/build for NVIDIA GPU hosts. -# Requiere drivers NVIDIA + NVIDIA Container Toolkit (Dockerfile.cuda). -# Install: pip install -r requirements-cuda.txt -# =================================================================== - --r requirements.txt - -# PyTorch CUDA 12.8 wheels (prebuilt) -torch --index-url https://download.pytorch.org/whl/cu128 -torchvision --index-url https://download.pytorch.org/whl/cu128 - -# Llama compiled with CUDA offload (needs cmake + CUDA devel headers) -llama-cpp-python -C cmake.args="-DGGML_CUDA=on" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 90f6fee8e..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,8 +0,0 @@ -pre-commit -ruff -sphinx_rtd_theme -sphinx -sqlalchemy-stubs -pytest -pytest-cov -pytest-asyncio diff --git a/requirements-plugins.txt b/requirements-plugins.txt deleted file mode 100644 index 356960f3a..000000000 --- a/requirements-plugins.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch==1.13.0 #+cu116 ---find-links https://download.pytorch.org/whl/torch_stable.html diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 165d4afe6..000000000 --- a/requirements.txt +++ /dev/null @@ -1,52 +0,0 @@ -# Nota: Tuvimos que volver a usar este tipo de requirements en vez de compilar el .in -# pip-compile rompia todo el CI/CD ya que no dejaba elegir bien que version de torch usar -setuptools>=65.0.0,<82 -fastapi[all] -SQLAlchemy -streaming_form_data -alembic -kink -numpy -pandas<3.0.0 -joblib -pydantic -pydantic-settings -starlette -scikit-learn<1.8.0 -datasets -diffusers -evaluate -accelerate -Pillow -beartype -plotly -shap -typer -rich -torch -torchvision -transformers -controlnet_aux -sacrebleu -sentencepiece -optuna -cmaes -hyperopt -nvidia-ml-py -openpyxl -httpx -wordcloud -opencv-python -protobuf -imblearn -numba -llvmlite -huey -ijson -greenery==3.2 -xlrd -filetype -torchmetrics -pywebview -openml -oslo.concurrency diff --git a/scripts/render_institutions.py b/scripts/render_institutions.py index 161e717d7..ab5cc4560 100644 --- a/scripts/render_institutions.py +++ b/scripts/render_institutions.py @@ -161,7 +161,7 @@ def render_block(data): ] for inst in data["institutions"]: - lines.append(f"* `{inst['name']} <{inst['url']}>`_ - {inst['role']}") + lines.append(f"* `{inst['name']} <{inst['url']}>`_ - {inst['role']['en']}") lines.append("") lines.append(data["acknowledgments"]["text"]["en"]) diff --git a/setup.py b/setup.py deleted file mode 100644 index 1688e7825..000000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -import os - -from setuptools import find_packages, setup - -with open("README.rst") as f: - long_description = f.read() - - -def load_requirements(filename): - """Load requirements from a file, ignoring comments and empty lines.""" - with open(os.path.join(os.path.dirname(__file__), filename)) as f: - return [line.strip() for line in f if line.strip() and not line.startswith("#")] - - -# Use your existing requirements files -requirements = load_requirements("requirements.txt") -test_requirements = load_requirements("requirements-dev.txt") - - -setup( - name="DashAI", - version="0.9.6", - license="MIT", - description=( - "DashAI: a graphical toolbox for training, evaluating and deploying " - "state-of-the-art AI models." - ), - long_description=long_description, - long_description_content_type="text/x-rst", - url="https://github.com/DashAISoftware/DashAI", - project_urls={ - "Documentation": "https://dash-ai.com/", - "Changelog": "https://dash-ai.com/changelog.html", - "Issue Tracker": "https://github.com/DashAISoftware/DashAI/issues", - }, - author="DashAI Team", - author_email="fbravo@dcc.uchile.cl", - packages=find_packages(), - include_package_data=True, - python_requires=">=3.10", - install_requires=requirements, - tests_require=test_requirements, - classifiers=[ - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - entry_points={ - "console_scripts": [ - "dashai = DashAI.__main__:run", - "DashAI = DashAI.__main__:run", - ] - }, -) diff --git a/tests/back/api/conftest.py b/tests/back/api/conftest.py index f9dc1ed9b..074e299f1 100644 --- a/tests/back/api/conftest.py +++ b/tests/back/api/conftest.py @@ -46,6 +46,23 @@ def client(test_path: Path): remove_dir_with_retry(app.container._services["config"]["LOCAL_PATH"]) +@pytest.fixture(scope="module", autouse=True) +def _mark_stable_diffusion2_downloaded(client): + """Make ``StableDiffusion2`` appear downloaded for session/run tests. + + All generative models now require a download, so the download gate would + reject session/run creation. Creating the component's repo folder lets the + filesystem-reconciled gate treat it as available without fetching weights. + """ + config = client.app.container._services["config"] + repo_dir = ( + Path(config["COMPONENT_PATH"]) / "StableDiffusion2" / "stable-diffusion-2" + ) + repo_dir.mkdir(parents=True, exist_ok=True) + (repo_dir / "config.json").write_text("{}", encoding="utf-8") + return + + @pytest.fixture(name="dataset_1", scope="module") def create_dataset_1(client) -> Dataset: """Create testing dataset 1 using job system.""" diff --git a/tests/back/api/test_component_download_endpoints.py b/tests/back/api/test_component_download_endpoints.py new file mode 100644 index 000000000..9405f8806 --- /dev/null +++ b/tests/back/api/test_component_download_endpoints.py @@ -0,0 +1,127 @@ +"""Tests for the component download/delete/status endpoints.""" + +import pytest +from kink import di + + +@pytest.fixture +def fake_downloadable_registry(): + """Swap di with a minimal registry containing a downloadable component.""" + + class FakeDownloadable: + REQUIRES_DOWNLOAD = True + _delete_called = False + + @classmethod + def delete(cls): + cls._delete_called = True + + class FakeRegistry: + def __getitem__(self, name): + if name == "FakeComponent": + return {"class": FakeDownloadable} + raise KeyError(f"Component {name!r} not found") + + def refresh_download_status(self, name): + return False + + class FakeJob: + id = "job-xyz" + + class FakeJobQueue: + def put(self, job): + return FakeJob() + + old_registry = di["component_registry"] + old_queue = di["job_queue"] + di["component_registry"] = FakeRegistry() + di["job_queue"] = FakeJobQueue() + yield + di["component_registry"] = old_registry + di["job_queue"] = old_queue + + +@pytest.fixture +def fake_non_downloadable_registry(): + """Swap di with a minimal registry containing a non-downloadable component.""" + + class FakeNonDownloadable: + REQUIRES_DOWNLOAD = False + + class FakeRegistry: + def __getitem__(self, name): + if name == "FakeComponent": + return {"class": FakeNonDownloadable} + raise KeyError(f"Component {name!r} not found") + + def refresh_download_status(self, name): + return False + + old_registry = di["component_registry"] + di["component_registry"] = FakeRegistry() + yield + di["component_registry"] = old_registry + + +def test_post_download_enqueue_201(client, fake_downloadable_registry): + resp = client.post("/api/v1/component/FakeComponent/download") + assert resp.status_code == 201 + assert resp.json() == {"id": "job-xyz"} + + +def test_delete_non_downloadable_409(client, fake_non_downloadable_registry): + resp = client.delete("/api/v1/component/FakeComponent/download") + assert resp.status_code == 409 + + +def test_get_download_status_nondownloadable(client): + # Any component that does NOT require download should return the status dict. + models = client.get("/api/v1/component/", params={"select_types": ["Model"]}).json() + non_downloadable = [m for m in models if not m["metadata"]["requires_download"]] + if not non_downloadable: + return # skip if all models require download (unlikely) + name = non_downloadable[0]["name"] + resp = client.get(f"/api/v1/component/{name}/download") + assert resp.status_code == 200 + body = resp.json() + assert "downloaded" in body + assert "requires_download" in body + assert body["requires_download"] is False + + +def test_get_download_status(client): + # Pick any downloadable Model from the list. + models = client.get("/api/v1/component/", params={"select_types": ["Model"]}).json() + downloadable = [m for m in models if m["metadata"]["requires_download"]] + if not downloadable: + return # no downloadable component registered in this environment + name = downloadable[0]["name"] + resp = client.get(f"/api/v1/component/{name}/download") + assert resp.status_code == 200 + assert "downloaded" in resp.json() + + +def test_get_download_status_unknown_404(client): + resp = client.get("/api/v1/component/NopeComponent/download") + assert resp.status_code == 404 + + +def test_download_nonexistent_component_404(client): + resp = client.post("/api/v1/component/NopeComponent/download") + assert resp.status_code == 404 + + +def test_post_download_non_downloadable_409(client): + # A component that does not require download should yield 409. + models = client.get("/api/v1/component/", params={"select_types": ["Model"]}).json() + non_downloadable = [m for m in models if not m["metadata"]["requires_download"]] + if not non_downloadable: + return + name = non_downloadable[0]["name"] + resp = client.post(f"/api/v1/component/{name}/download") + assert resp.status_code == 409 + + +def test_delete_download_unknown_404(client): + resp = client.delete("/api/v1/component/NopeComponent/download") + assert resp.status_code == 404 diff --git a/tests/back/api/test_components_api.py b/tests/back/api/test_components_api.py index 26cd14699..6f598d304 100644 --- a/tests/back/api/test_components_api.py +++ b/tests/back/api/test_components_api.py @@ -164,6 +164,7 @@ def test_get_component_by_id(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, } response = client.get("/api/v1/component/TestTask2/") @@ -182,6 +183,7 @@ def test_get_component_by_id(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, } response = client.get("/api/v1/component/TestDataloader1/") @@ -199,6 +201,7 @@ def test_get_component_by_id(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, } @@ -292,6 +295,7 @@ def test_get_components_select_only_tasks(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, }, { "name": "TestTask2", @@ -307,6 +311,7 @@ def test_get_components_select_only_tasks(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, }, ] @@ -330,6 +335,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -344,6 +350,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -358,6 +365,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -438,6 +446,7 @@ def test_get_components_ignore_models(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, }, { "name": "TestTask2", @@ -453,6 +462,7 @@ def test_get_components_ignore_models(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader1", @@ -467,6 +477,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -481,6 +492,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -495,6 +507,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -517,6 +530,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -531,6 +545,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -545,6 +560,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -608,6 +624,7 @@ def test_get_components_related_inverse_relation(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, } ] @@ -653,6 +670,7 @@ def test_get_components_dataloader_component_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -667,6 +685,7 @@ def test_get_components_dataloader_component_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -706,6 +725,7 @@ def test_get_components_by_type_and_task(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -720,6 +740,7 @@ def test_get_components_by_type_and_task(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -758,6 +779,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -772,6 +794,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -786,6 +809,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -811,6 +835,7 @@ def test_get_components_select_type_and_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -825,5 +850,6 @@ def test_get_components_select_type_and_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] diff --git a/tests/back/api/test_components_download_fields.py b/tests/back/api/test_components_download_fields.py new file mode 100644 index 000000000..5c5c00e8e --- /dev/null +++ b/tests/back/api/test_components_download_fields.py @@ -0,0 +1,43 @@ +"""Test that components API exposes download-related fields.""" + + +def test_components_list_includes_download_fields(client): + """Verify that the components list endpoint includes download fields. + + Parameters + ---------- + client : TestClient + The FastAPI test client fixture. + """ + response = client.get("/api/v1/component/", params={"select_types": ["Model"]}) + assert response.status_code == 200 + components = response.json() + assert components, "expected at least one Model component" + for component in components: + assert "downloaded" in component + assert "requires_download" in component["metadata"] + assert "download_size_bytes" in component["metadata"] + + +def test_component_by_id_includes_download_fields(client): + """Verify that the single component endpoint includes download fields. + + Parameters + ---------- + client : TestClient + The FastAPI test client fixture. + """ + # Get a Model component to test + response = client.get("/api/v1/component/", params={"select_types": ["Model"]}) + assert response.status_code == 200 + components = response.json() + assert components, "expected at least one Model component" + + # Test the first component via direct ID endpoint + component_id = components[0]["name"] + response = client.get(f"/api/v1/component/{component_id}/") + assert response.status_code == 200 + component = response.json() + assert "downloaded" in component + assert "requires_download" in component["metadata"] + assert "download_size_bytes" in component["metadata"] diff --git a/tests/back/api/test_components_download_required.py b/tests/back/api/test_components_download_required.py new file mode 100644 index 000000000..a7b63544b --- /dev/null +++ b/tests/back/api/test_components_download_required.py @@ -0,0 +1,79 @@ +"""Tests for the nested download-resolution endpoint.""" + +import pytest +from fastapi.testclient import TestClient + +from DashAI.back.dependencies.downloads.downloadable import DownloadableMixin +from DashAI.back.dependencies.registry import ComponentRegistry +from DashAI.back.models.base_model import BaseModel + +URL = "/api/v1/component/downloads/required" + + +class PlainModel(BaseModel): + """A model with no download requirement.""" + + @classmethod + def get_schema(cls) -> dict: + return {} + + def save(self, filename=None): ... + + def load(self, filename): ... + + +class DownloadableModel(DownloadableMixin, BaseModel): + """A nested-selectable model that reports as not downloaded.""" + + DOWNLOAD_SIZE_BYTES = 123 + DESCRIPTION = "Downloadable" + DISPLAY_NAME = "Downloadable Model" + + @classmethod + def is_downloaded(cls) -> bool: + return False + + @classmethod + def get_schema(cls) -> dict: + return {} + + def save(self, filename=None): ... + + def load(self, filename): ... + + +@pytest.fixture(autouse=True) +def _registry(client, monkeypatch): + registry = ComponentRegistry(initial_components=[PlainModel, DownloadableModel]) + monkeypatch.setitem(client.app.container._services, "component_registry", registry) + return registry + + +def test_required_downloads_reports_nested(client: TestClient): + body = { + "parameters": { + "clf": {"component": "DownloadableModel", "params": {}}, + } + } + response = client.post(URL, json=body) + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["name"] == "DownloadableModel" + assert data[0]["download_size_bytes"] == 123 + assert data[0]["display_name"] == "Downloadable Model" + + +def test_required_downloads_empty_for_plain(client: TestClient): + body = {"parameters": {"clf": {"component": "PlainModel", "params": {}}}} + response = client.post(URL, json=body) + assert response.status_code == 200 + assert response.json() == [] + + +def test_required_downloads_includes_parent_model(client: TestClient): + body = {"model_name": "DownloadableModel", "parameters": {}} + response = client.post(URL, json=body) + assert response.status_code == 200 + data = response.json() + assert [d["name"] for d in data] == ["DownloadableModel"] diff --git a/tests/back/api/test_explainer_jobs.py b/tests/back/api/test_explainer_jobs.py index c63e11b8f..2b4bd0b3f 100644 --- a/tests/back/api/test_explainer_jobs.py +++ b/tests/back/api/test_explainer_jobs.py @@ -122,6 +122,55 @@ def plot(self, explanation): return +class MangleModel(DummyModel): + """Model whose preparation renames the input columns. + + Mirrors bag-of-words style text models, whose ``prepare_dataset`` + replaces the raw input column with the vectorized feature columns. + """ + + COMPATIBLE_COMPONENTS = ["DummyTask"] + + @staticmethod + def load(filename): + return MangleModel() + + def prepare_dataset(self, dataset, is_fit=False): + return dataset.rename_columns( + {column: f"{column}_prepared" for column in dataset.column_names} + ) + + +RAW_INPUT_COLUMNS = [] + + +class RawInputLocalExplainer(BaseLocalExplainer): + """Local explainer that records the columns the job hands it.""" + + COMPATIBLE_COMPONENTS = ["DummyTask"] + + def __init__(self, model: BaseModel) -> None: + self.model = model + self.explanation = None + + @classmethod + def get_schema(cls): + return {} + + def fit(self, dataset, **kwargs): + return self + + def explain_instance(self, instances): + columns = instances.column_names + if isinstance(columns, dict): + columns = [column for split in columns.values() for column in split] + RAW_INPUT_COLUMNS.extend(columns) + return {} + + def plot(self, explanation): + return + + @pytest.fixture(autouse=True, name="test_registry") def setup_test_registry(client, monkeypatch: pytest.MonkeyPatch): """Setup a test registry with test task and explainers components.""" @@ -131,8 +180,10 @@ def setup_test_registry(client, monkeypatch: pytest.MonkeyPatch): initial_components=[ DummyTask, DummyModel, + MangleModel, DummyGlobalExplainer, DummyLocalExplainer, + RawInputLocalExplainer, ExplainerJob, ] ) @@ -406,6 +457,80 @@ def test_execute_jobs( ) +def test_local_explainer_receives_unprepared_model_input( + client: TestClient, model_session_id: int, dataset_id: int +): + """The job hands over the instances without the model preparation. + + Explainers query ``model.predict``, which prepares its input itself, so + preparing beforehand would break models that replace the input column + with derived features (bag-of-words counts, for instance). Explainers + needing the model feature space ask for it with ``prepare_model_input``. + """ + container = client.app.container + session_factory = container["session_factory"] + RAW_INPUT_COLUMNS.clear() + + with session_factory() as db: + run = Run( + model_session_id=model_session_id, + optimizer_name="OptunaOptimizer", + optimizer_parameters={ + "n_trials": 1, + "sampler": "TPESampler", + "pruner": "None", + }, + model_name="MangleModel", + parameters={}, + goal_metric="Accuracy", + name="RawInputRun", + split_indexes="""{ + "train_indexes": [0, 1, 2, 3, 4], + "test_indexes": [5, 6, 7, 8], + "val_indexes": [9, 10, 11, 12] + }""", + ) + db.add(run) + db.commit() + db.refresh(run) + + explainer = LocalExplainer( + name="test_raw_local", + run_id=run.id, + explainer_name="RawInputLocalExplainer", + dataset_id=dataset_id, + scope={"split": "test", "percentage": 100}, + parameters={}, + fit_parameters={}, + ) + db.add(explainer) + db.commit() + db.refresh(explainer) + explainer_id = explainer.id + + response = client.post( + "/api/v1/job/", + data={ + "job_type": "ExplainerJob", + "kwargs": json.dumps( + {"explainer_id": explainer_id, "explainer_scope": "local"} + ), + }, + ) + assert response.status_code == 201, response.text + job_id = response.json()["id"] + + job_status = client.get(f"/api/v1/job/status/{job_id}").json() + assert job_status["status"] == "finished", ( + f"Job should be finished, got {job_status['status']}" + ) + + assert RAW_INPUT_COLUMNS, "The explainer never received any instance" + assert set(RAW_INPUT_COLUMNS) == set(input_columns), ( + f"Explainer got prepared columns {sorted(set(RAW_INPUT_COLUMNS))}" + ) + + def test_job_with_wrong_explainer(client: TestClient): form_data_wrong = { "job_type": "ExplainerJob", diff --git a/tests/back/api/test_explainers_overrides.py b/tests/back/api/test_explainers_overrides.py new file mode 100644 index 000000000..f003139a5 --- /dev/null +++ b/tests/back/api/test_explainers_overrides.py @@ -0,0 +1,61 @@ +"""Unit tests for the ``_apply_overrides`` helper in explainers endpoints. + +These tests import only the pure helper function, not the FastAPI app, so +they can run without the heavy explainer dependencies (grad_cam, dice_ml, +lime) that are currently missing from the project venv and would otherwise +be pulled in by component registration when booting a TestClient. + +NOTE: The HTTP round-trip tests for the override endpoints (PUT/DELETE +``/{scope}/plot/{explainer_id}/override``) described in the task brief are +deferred until the environment has the explainer dependencies installed, so +a TestClient can be instantiated without import errors. +""" + +import json + +from DashAI.back.api.api_v1.endpoints.explainers import _apply_overrides + + +def test_apply_overrides_replaces_plotly_payload(): + """An override at a plotly artifact's index replaces its payload.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0", "index": 0}, + ] + figure = {"data": [], "layout": {"title": "edited"}} + + result = _apply_overrides(artifacts, {"0": figure}) + + assert result[0]["payload"] != "original" + assert json.loads(result[0]["payload"]) == figure + + +def test_apply_overrides_leaves_non_plotly_artifact_unchanged(): + """An override targeting a non-plotly artifact index is ignored.""" + artifacts = [ + {"type": "image", "payload": "original-image", "title": "Image 0", "index": 0}, + ] + + result = _apply_overrides(artifacts, {"0": {"data": [], "layout": {}}}) + + assert result[0]["payload"] == "original-image" + + +def test_apply_overrides_ignores_out_of_range_index(): + """An override with an out-of-range index does not raise or mutate.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0", "index": 0}, + ] + + result = _apply_overrides(artifacts, {"5": {"data": []}}) + + assert result[0]["payload"] == "original" + + +def test_apply_overrides_returns_unchanged_for_none_or_empty(): + """None or empty overrides leave the artifacts list unchanged.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0"}, + ] + + assert _apply_overrides(artifacts, None) == artifacts + assert _apply_overrides(artifacts, {}) == artifacts diff --git a/tests/back/api/test_explanations_api.py b/tests/back/api/test_explanations_api.py index bc84710ca..d3956a603 100644 --- a/tests/back/api/test_explanations_api.py +++ b/tests/back/api/test_explanations_api.py @@ -143,7 +143,6 @@ def test_create_global_explainer(client: TestClient, run_id_1: int, run_id_2: in response = client.post( "/api/v1/explainer/global", json={ - "name": "test_1", "run_id": run_id_1, "explainer_name": "PartialDependence", "parameters": { @@ -159,7 +158,6 @@ def test_create_global_explainer(client: TestClient, run_id_1: int, run_id_2: in response = client.post( "/api/v1/explainer/global", json={ - "name": "test_2", "run_id": run_id_2, "explainer_name": "PermutationFeatureImportance", "parameters": { @@ -188,7 +186,6 @@ def test_create_local_explainer(client: TestClient, dataset_id: int, run_id_1: i response = client.post( "/api/v1/explainer/local", json={ - "name": "test_1", "run_id": run_id_1, "dataset_id": dataset_id, "explainer_name": "KernelShap", @@ -220,7 +217,6 @@ def test_get_global_explainers_by_run_id(client: TestClient, run_id_1: int): response = client.post( "/api/v1/explainer/global", json={ - "name": "test_1", "run_id": run_id_1, "explainer_name": "PartialDependence", "parameters": { @@ -236,7 +232,6 @@ def test_get_global_explainers_by_run_id(client: TestClient, run_id_1: int): response = client.post( "/api/v1/explainer/global", json={ - "name": "test_2", "run_id": run_id_1, "explainer_name": "PermutationFeatureImportance", "parameters": { @@ -252,7 +247,6 @@ def test_get_global_explainers_by_run_id(client: TestClient, run_id_1: int): response = client.get("/api/v1/explainer/global/?run_id=1") assert response.status_code == 200, response.text data = response.json() - assert data[0]["name"] == "test_1" assert data[0]["run_id"] == run_id_1 assert data[0]["explainer_name"] == "PartialDependence" assert data[0]["parameters"] == { @@ -262,7 +256,6 @@ def test_get_global_explainers_by_run_id(client: TestClient, run_id_1: int): "upper_percentile": 1, } - assert data[1]["name"] == "test_2" assert data[1]["run_id"] == run_id_1 assert data[1]["explainer_name"] == "PermutationFeatureImportance" assert data[1]["parameters"] == { @@ -290,7 +283,6 @@ def test_get_local_explainers_by_run_id( response = client.post( "/api/v1/explainer/local", json={ - "name": "test_1", "run_id": run_id_1, "dataset_id": dataset_id, "explainer_name": "KernelShap", @@ -311,7 +303,6 @@ def test_get_local_explainers_by_run_id( response = client.get("/api/v1/explainer/local/?run_id=1") assert response.status_code == 200, response.text data = response.json() - assert data[0]["name"] == "test_1" assert data[0]["run_id"] == run_id_1 assert data[0]["explainer_name"] == "KernelShap" assert data[0]["parameters"] == { @@ -338,7 +329,6 @@ def test_get_global_explanation(client: TestClient, run_id_1: int): response = client.post( "/api/v1/explainer/global", json={ - "name": "test_1", "run_id": run_id_1, "explainer_name": "PartialDependence", "parameters": { @@ -372,7 +362,6 @@ def test_get_local_explanation(client: TestClient, dataset_id: int, run_id_1: in response = client.post( "/api/v1/explainer/local", json={ - "name": "test_1", "run_id": run_id_1, "dataset_id": dataset_id, "explainer_name": "KernelShap", @@ -407,7 +396,6 @@ def test_delete_global_explainer(client: TestClient, run_id_1: int): response = client.post( "/api/v1/explainer/global", json={ - "name": "test_1", "run_id": run_id_1, "explainer_name": "PartialDependence", "parameters": { @@ -428,7 +416,6 @@ def test_delete_local_explainer(client: TestClient, dataset_id: int, run_id_1: i response = client.post( "/api/v1/explainer/local", json={ - "name": "test_2", "run_id": run_id_1, "explainer_name": "KernelShap", "dataset_id": dataset_id, diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py new file mode 100644 index 000000000..c454ca785 --- /dev/null +++ b/tests/back/api/test_generative_session_download_gate.py @@ -0,0 +1,192 @@ +"""Tests for the generative-session creation download gate.""" + +from kink import di + +_SESSION_PAYLOAD_BASE = { + "parameters": {}, + "name": "gen-gate-test-session", + "description": None, +} + + +class _FakeDownloadableGenerativeModel: + """Minimal stub: download-required generative model that is not downloaded.""" + + REQUIRES_DOWNLOAD = True + + @classmethod + def is_downloaded(cls): + return False + + +class _FakeGenerativeRegistry: + """Registry wrapper that injects FakeDownloadableGenerativeModel.""" + + def __init__(self, real): + self._real = real + + def __getitem__(self, name): + if name == "FakeDownloadableGenerativeModel": + return { + "class": _FakeDownloadableGenerativeModel, + "downloaded": False, + } + return self._real[name] + + def get_components_by_types(self, select=None, ignore=None): + return self._real.get_components_by_types(select=select, ignore=ignore) + + def refresh_download_status(self, name): + if name == "FakeDownloadableGenerativeModel": + return _FakeDownloadableGenerativeModel.is_downloaded() + return self._real.refresh_download_status(name) + + def __contains__(self, name): + return name == "FakeDownloadableGenerativeModel" or name in self._real + + +def test_upload_generative_session_rejects_undownloaded_model(client): + """Creating a session for a not-yet-downloaded model must return HTTP 409.""" + old = di["component_registry"] + di["component_registry"] = _FakeGenerativeRegistry(old) + try: + resp = client.post( + "/api/v1/generative-session/", + json={ + "model_name": "FakeDownloadableGenerativeModel", + "task_name": "TextToTextGenerationTask", + **_SESSION_PAYLOAD_BASE, + }, + ) + finally: + di["component_registry"] = old + + assert resp.status_code == 409 + assert "download" in resp.json()["detail"].lower() + + +def test_upload_generative_session_unknown_model_400(client): + """POSTing a session with an unregistered model_name must return HTTP 400.""" + resp = client.post( + "/api/v1/generative-session/", + json={ + "model_name": "__totally_bogus_generative_model_xyz__", + "task_name": "TextToTextGenerationTask", + **_SESSION_PAYLOAD_BASE, + }, + ) + assert resp.status_code == 400 + assert "is not registered" in resp.json()["detail"] + + +_SD_PARAMS = { + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, + "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, +} + + +def _create_sd_session(client, name): + return client.post( + "/api/v1/generative-session/", + json={ + "model_name": "StableDiffusion2", + "task_name": "TextToImageGenerationTask", + "parameters": _SD_PARAMS, + "name": name, + "description": None, + }, + ) + + +def test_change_session_model_to_undownloaded_succeeds(client): + """Switching to a not-downloaded model is allowed; the chat gates its use. + + A user may point a session at any registered generative model even if its + weights are not present yet; the download is offered from the chat instead + of being blocked at switch time. + """ + created = _create_sd_session(client, "gen-switch-undownloaded") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "Qwen25_15BInstruct"}, + ) + assert resp.status_code == 200 + assert resp.json()["model_name"] == "Qwen25_15BInstruct" + + client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_change_session_model_unknown_returns_400(client): + """Switching a session to an unregistered model must return HTTP 400.""" + created = _create_sd_session(client, "gen-switch-400") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "__totally_bogus_generative_model_xyz__"}, + ) + assert resp.status_code == 400 + assert "is not registered" in resp.json()["detail"] + + client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_change_session_model_valid_returns_200(client): + """Switching a session to a valid (non-download) model must succeed.""" + created = _create_sd_session(client, "gen-switch-200") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "StableDiffusion2"}, + ) + assert resp.status_code == 200 + assert resp.json()["model_name"] == "StableDiffusion2" + + client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_switch_model_resets_params_and_records_history(client): + """A switch resets params to the new model's defaults and logs the change.""" + created = _create_sd_session(client, "gen-switch-history") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "Qwen25_15BInstruct"}, + ) + assert resp.status_code == 200 + params = resp.json()["parameters"] + # Parameters were reset to the target model's own fields, not carried over + # from the Stable Diffusion session. + assert "num_inference_steps" not in params + assert "max_tokens" in params + + history = client.get(f"/api/v1/generative-session/parameters-history/{session_id}") + assert history.status_code == 200 + model_changes = [ + change + for event in history.json() + for change in event["changes"] + if change["parameter"] == "model" + ] + assert { + "parameter": "model", + "oldValue": "StableDiffusion2", + "newValue": "Qwen25_15BInstruct", + } in model_changes + + client.delete(f"/api/v1/generative-session/{session_id}") diff --git a/tests/back/api/test_jobs.py b/tests/back/api/test_jobs.py index 9ac229fc2..31ef9f53c 100644 --- a/tests/back/api/test_jobs.py +++ b/tests/back/api/test_jobs.py @@ -78,10 +78,18 @@ def score(true_labels: list, probs_pred_labels: list): return 1 -@pytest.fixture(autouse=True, name="test_registry") -def setup_test_registry(client, monkeypatch: pytest.MonkeyPatch): - """Setup a test registry with test task, dataloader and model components.""" +@pytest.fixture(scope="module", name="test_registry", autouse=True) +def setup_test_registry(client): + """Setup a test registry with test task, dataloader and model components. + + Module-scoped (with a manual swap, since ``monkeypatch`` is function + scoped) so the test components are registered before the module-scoped + ``create_run`` / ``create_model_session`` fixtures run. + """ container = client.app.container + sentinel = object() + services = container._services + old = services.get("component_registry", sentinel) test_registry = ComponentRegistry( initial_components=[ @@ -94,13 +102,12 @@ def setup_test_registry(client, monkeypatch: pytest.MonkeyPatch): OptunaOptimizer, ] ) - - monkeypatch.setitem( - container._services, - "component_registry", - test_registry, - ) - return test_registry + services["component_registry"] = test_registry + yield test_registry + if old is sentinel: + del services["component_registry"] + else: + services["component_registry"] = old @pytest.fixture(scope="module", name="dataset_id") @@ -110,7 +117,7 @@ def dataset_id(dataset_1: Dataset) -> int: @pytest.fixture(scope="module", name="model_session_id", autouse=True) -def create_model_session(client: TestClient, dataset_id: int): +def create_model_session(client: TestClient, dataset_id: int, test_registry): container = client.app.container session = container["session_factory"] @@ -150,7 +157,7 @@ def create_model_session(client: TestClient, dataset_id: int): @pytest.fixture(scope="module", name="run_id", autouse=True) -def create_run(client: TestClient, model_session_id: int): +def create_run(client: TestClient, model_session_id: int, test_registry): response = client.post( "/api/v1/run/", json={ @@ -182,7 +189,7 @@ def create_run(client: TestClient, model_session_id: int): @pytest.fixture(scope="module", name="failed_run_id", autouse=True) -def create_failed_run(client: TestClient, model_session_id: int): +def create_failed_run(client: TestClient, model_session_id: int, test_registry): container = client.app.container session_factory = container["session_factory"] diff --git a/tests/back/api/test_predict_api.py b/tests/back/api/test_predict_api.py index c81c2bf94..429329ade 100644 --- a/tests/back/api/test_predict_api.py +++ b/tests/back/api/test_predict_api.py @@ -341,12 +341,12 @@ def test_filter_datasets_endpoint( params={"run_id": trained_run_id}, ) assert response.status_code == 200, response.text - datasets = response.json() - assert isinstance(datasets, list) - assert len(datasets) == 1 - dataset_names = [ds["name"] for ds in datasets] - assert dataset["name"] in dataset_names - assert dataset_2["name"] not in dataset_names + body = response.json() + valid_dataset_ids = body["valid_dataset_ids"] + assert isinstance(valid_dataset_ids, list) + assert len(valid_dataset_ids) == 1 + assert dataset["id"] in valid_dataset_ids + assert dataset_2["id"] not in valid_dataset_ids def test_delete_prediction(client: TestClient, trained_run_id: int): diff --git a/tests/back/api/test_process_api.py b/tests/back/api/test_process_api.py index 7a65cbea5..35b1fcfd7 100644 --- a/tests/back/api/test_process_api.py +++ b/tests/back/api/test_process_api.py @@ -6,7 +6,7 @@ def session(client: TestClient): """Create a valid session for process tests.""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": { "num_inference_steps": 1, diff --git a/tests/back/api/test_run_download_gate.py b/tests/back/api/test_run_download_gate.py new file mode 100644 index 000000000..52af92061 --- /dev/null +++ b/tests/back/api/test_run_download_gate.py @@ -0,0 +1,132 @@ +"""Tests for the run-creation download gate (Task 8).""" + +from kink import di + +_RUN_PAYLOAD_BASE = { + "parameters": {}, + "optimizer_name": "OptunaOptimizer", + "optimizer_parameters": {}, + "goal_metric": "Accuracy", + "plot_history_path": "", + "plot_slice_path": "", + "plot_contour_path": "", + "plot_importance_path": "", + "name": "gate-test-run", + "description": "", +} + + +class _FakeDownloadableModel: + """Minimal stub that looks like an undownloaded, download-required model.""" + + REQUIRES_DOWNLOAD = True + + @classmethod + def is_downloaded(cls): + return False + + +class _FakeRegistry: + """Registry wrapper that exposes FakeDownloadableModel on top of the real one.""" + + def __init__(self, real): + self._real = real + + def __getitem__(self, name): + if name == "FakeDownloadableModel": + return {"class": _FakeDownloadableModel, "downloaded": False} + return self._real[name] + + def get_components_by_types(self, select=None, ignore=None): + return self._real.get_components_by_types(select=select, ignore=ignore) + + def refresh_download_status(self, name): + if name == "FakeDownloadableModel": + return _FakeDownloadableModel.is_downloaded() + return self._real.refresh_download_status(name) + + def __contains__(self, name): + return name == "FakeDownloadableModel" or name in self._real + + +def _make_model_session(client, suffix=""): + """Insert a Dataset + ModelSession row and return the ModelSession id.""" + import uuid + + from DashAI.back.dependencies.database.models import Dataset, ModelSession + + unique = suffix or uuid.uuid4().hex[:8] + sf = client.app.container["session_factory"] + with sf() as db: + ds = Dataset(name=f"__gate_test_ds_{unique}__", file_path="") + db.add(ds) + db.flush() + ms = ModelSession( + dataset_id=ds.id, + name=f"__gate_test_ms_{unique}__", + task_name="TabularClassificationTask", + input_columns=[], + output_columns=[], + train_metrics=[], + validation_metrics=[], + test_metrics=[], + splits={}, + ) + db.add(ms) + db.commit() + db.refresh(ms) + return ms.id + + +def test_upload_run_rejects_undownloaded_model_synthetic(client): + """Creating a run for a not-yet-downloaded model must return HTTP 409.""" + ms_id = _make_model_session(client) + old = di["component_registry"] + di["component_registry"] = _FakeRegistry(old) + try: + resp = client.post( + "/api/v1/run/", + json={ + "model_session_id": ms_id, + "model_name": "FakeDownloadableModel", + **_RUN_PAYLOAD_BASE, + }, + ) + finally: + di["component_registry"] = old + + assert resp.status_code == 409 + assert "download" in resp.json()["detail"].lower() + + +def test_upload_run_rejects_undownloaded_model(client, monkeypatch): + """Force a real downloadable model undownloaded; run creation must return 409.""" + registry = di["component_registry"] + models = client.get("/api/v1/component/", params={"select_types": ["Model"]}).json() + downloadable = [m for m in models if m["metadata"]["requires_download"]] + if not downloadable: + return # no downloadable model registered in this environment; skip + name = downloadable[0]["name"] + monkeypatch.setitem(registry[name], "downloaded", False) + + resp = client.post( + "/api/v1/run/", + json={"model_session_id": 1, "model_name": name, **_RUN_PAYLOAD_BASE}, + ) + assert resp.status_code in (404, 409) + if resp.status_code == 409: + assert "download" in resp.json()["detail"].lower() + + +def test_upload_run_unknown_model_422(client): + """POSTing a run with an unregistered model_name must return HTTP 422.""" + ms_id = _make_model_session(client) + resp = client.post( + "/api/v1/run/", + json={ + "model_session_id": ms_id, + "model_name": "__totally_bogus_model_xyz__", + **_RUN_PAYLOAD_BASE, + }, + ) + assert resp.status_code == 422 diff --git a/tests/back/api/test_session_api.py b/tests/back/api/test_session_api.py index 1572a28eb..bb8e2b411 100644 --- a/tests/back/api/test_session_api.py +++ b/tests/back/api/test_session_api.py @@ -6,7 +6,7 @@ def create_session_1(client: TestClient): """Create testing session 1 using job system.""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": { "num_inference_steps": 1, @@ -54,17 +54,20 @@ def create_session_2(client: TestClient): @pytest.fixture(scope="module", name="response_3") def create_session_3(client: TestClient): - """Create testing session 3 using job system.""" + """Create testing session 3 using a non-download-required model.""" params = { - "model_name": "QwenModel", - "task_name": "TextToTextGenerationTask", + "model_name": "StableDiffusion2", + "task_name": "TextToImageGenerationTask", "parameters": { - "model_name": "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - "max_tokens": 100, - "temperature": 0.9, - "frequency_penalty": 0.1, - "context_window": 512, + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, }, "name": "session_3", "description": None, @@ -80,17 +83,20 @@ def create_session_3(client: TestClient): @pytest.fixture(scope="module", name="response_4") def create_session_4(client: TestClient): - """Create testing session 4 using job system.""" + """Create testing session 4 with an invalid task (valid model).""" params = { - "model_name": "QwenModel", + "model_name": "StableDiffusion2", "task_name": "SomeTask", "parameters": { - "model_name": "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - "max_tokens": 100, - "temperature": 0.9, - "frequency_penalty": 0.1, - "context_window": 512, + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, }, "name": "session_4", "description": None, @@ -110,7 +116,7 @@ def test_create_session(response_1): data = response_1.json() assert data["id"] is not None, "Session ID is missing" assert data["name"] == "session_1", "Session name does not match" - assert data["model_name"] == "StableDiffusionV2Model", "Model name does not match" + assert data["model_name"] == "StableDiffusion2", "Model name does not match" assert data["task_name"] == "TextToImageGenerationTask", "Task name does not match" @@ -129,7 +135,7 @@ def test_get_session_by_id(client: TestClient, response_1): data = response.json() assert data["id"] == session_id, "Retrieved session ID does not match" assert data["name"] == "session_1", "Session name does not match" - assert data["model_name"] == "StableDiffusionV2Model", "Model name does not match" + assert data["model_name"] == "StableDiffusion2", "Model name does not match" assert data["task_name"] == "TextToImageGenerationTask", "Task name does not match" diff --git a/tests/back/core/__init__.py b/tests/back/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/back/core/test_artifacts.py b/tests/back/core/test_artifacts.py new file mode 100644 index 000000000..5d6e49f60 --- /dev/null +++ b/tests/back/core/test_artifacts.py @@ -0,0 +1,329 @@ +import base64 +import json + +import pytest + +from DashAI.back.core.artifacts import ( + Artifact, + ImageArtifact, + ImagePayload, + PlotlyArtifact, + TableArtifact, + TableCell, + TablePayload, + TextArtifact, + normalize_artifacts, +) + +PNG_BYTES = b"\x89PNG\r\n\x1a\n" + b"\x00" * 8 + + +def test_plotly_artifact_to_dict(): + artifact = PlotlyArtifact(payload='{"data": []}', title="A plot") + assert artifact.to_dict() == { + "type": "plotly", + "payload": '{"data": []}', + "title": "A plot", + "role": "explanation", + } + + +def test_plotly_artifact_accepts_figure(): + import plotly.graph_objects as go + + figure = go.Figure(data=[go.Bar(x=["a"], y=[1])]) + artifact = PlotlyArtifact(payload=figure) + parsed = json.loads(artifact.payload) + assert parsed["data"][0]["type"] == "bar" + + +def test_table_artifact_to_dict(): + artifact = TableArtifact( + payload=TablePayload( + columns=["a", "b"], + rows=[[1, 2], [3, 4]], + highlight=[TableCell(row=1, column=0)], + ), + title="A table", + ) + assert artifact.to_dict() == { + "type": "table", + "payload": { + "columns": ["a", "b"], + "rows": [[1, 2], [3, 4]], + "highlight": [{"row": 1, "column": 0}], + }, + "title": "A table", + "role": "explanation", + } + + +def test_table_artifact_rejects_ragged_rows(): + with pytest.raises(ValueError, match="expected 2"): + TablePayload(columns=["a", "b"], rows=[[1, 2], [3]]) + + +def test_table_artifact_rejects_out_of_bounds_highlight(): + with pytest.raises(ValueError, match="out of bounds"): + TablePayload( + columns=["a"], + rows=[[1]], + highlight=[TableCell(row=1, column=0)], + ) + + +def test_text_artifact_to_dict(): + artifact = TextArtifact(payload="line 1\nline 2") + assert artifact.to_dict() == { + "type": "text", + "payload": "line 1\nline 2", + "title": None, + "role": "explanation", + } + + +def test_image_artifact_encodes_bytes(): + artifact = ImageArtifact(payload=ImagePayload(data=PNG_BYTES)) + decoded = base64.b64decode(artifact.payload.data) + assert decoded == PNG_BYTES + + +def test_image_artifact_rejects_invalid_base64(): + with pytest.raises(ValueError, match="base64"): + ImagePayload(data="not base64!!") + + +def test_image_artifact_from_dashai_image(): + from DashAI.back.types.dashai_image import DashAIImage + + image = DashAIImage(bytes=PNG_BYTES, path="img.png") + artifact = ImageArtifact.from_dashai_image(image, title="An image") + assert artifact.payload.mime == "image/png" + assert base64.b64decode(artifact.payload.data) == PNG_BYTES + assert artifact.title == "An image" + + +def test_image_artifact_from_dashai_image_without_bytes(): + from DashAI.back.types.dashai_image import DashAIImage + + with pytest.raises(ValueError, match="no bytes"): + ImageArtifact.from_dashai_image(DashAIImage()) + + +@pytest.mark.parametrize( + "artifact", + [ + PlotlyArtifact(payload='{"data": []}', title="p"), + TableArtifact(payload=TablePayload(columns=["a"], rows=[[1]])), + TextArtifact(payload="hello"), + ImageArtifact(payload=ImagePayload(data=PNG_BYTES)), + ], +) +def test_from_dict_round_trip(artifact): + restored = Artifact.from_dict(artifact.to_dict()) + assert type(restored) is type(artifact) + assert restored == artifact + + +def test_from_dict_rejects_unknown_type(): + with pytest.raises(ValueError, match="Invalid artifact"): + Artifact.from_dict({"type": "hologram", "payload": "x"}) + + +def test_from_dict_rejects_malformed_payload(): + with pytest.raises(ValueError, match="Invalid artifact"): + Artifact.from_dict({"type": "table", "payload": {"columns": ["a"]}}) + + +def test_normalize_none_is_empty(): + assert normalize_artifacts(None) == [] + + +def test_normalize_legacy_plotly_strings(): + artifacts = normalize_artifacts(['{"data": []}']) + assert artifacts == [ + { + "type": "plotly", + "payload": '{"data": []}', + "title": None, + "role": "explanation", + "index": 0, + } + ] + + +def test_normalize_wraps_single_values(): + assert normalize_artifacts('{"data": []}')[0]["type"] == "plotly" + assert normalize_artifacts(TextArtifact(payload="x"))[0]["type"] == "text" + + +def test_normalize_artifact_instances(): + artifacts = normalize_artifacts([TextArtifact(payload="x", title="t")]) + assert artifacts == [ + { + "type": "text", + "payload": "x", + "title": "t", + "role": "explanation", + "index": 0, + } + ] + + +def test_normalize_passes_artifact_dicts_through(): + item = {"type": "text", "payload": "x"} + assert normalize_artifacts([item]) == [ + { + "type": "text", + "payload": "x", + "title": None, + "role": "explanation", + "index": 0, + } + ] + + +def test_normalize_legacy_explorer_plotly(): + legacy = {"type": "plotly_json", "data": '{"data": []}', "config": {}} + artifacts = normalize_artifacts([legacy]) + assert artifacts == [ + { + "type": "plotly", + "payload": '{"data": []}', + "title": None, + "role": "explanation", + "index": 0, + } + ] + + +def test_normalize_legacy_explorer_tabular(): + legacy = { + "type": "tabular", + "data": {"a": {"r1": 1, "r2": 2}, "b": {"r1": 3, "r2": 4}}, + "config": {"orient": "dict"}, + } + [artifact] = normalize_artifacts([legacy]) + assert artifact["type"] == "table" + assert artifact["payload"]["columns"] == ["index", "a", "b"] + assert artifact["payload"]["rows"] == [["r1", 1, 3], ["r2", 2, 4]] + + +def test_normalize_legacy_explorer_image(): + encoded = base64.b64encode(PNG_BYTES).decode("ascii") + legacy = {"type": "image_base64", "data": encoded, "config": {}} + [artifact] = normalize_artifacts([legacy]) + assert artifact["type"] == "image" + assert artifact["payload"]["data"] == encoded + + +def test_normalize_unrenderable_falls_back_to_text(): + [artifact] = normalize_artifacts([42]) + assert artifact == { + "type": "text", + "payload": "42", + "title": None, + "role": "explanation", + "index": 0, + } + + +def test_normalize_wraps_local_explainer_items_in_grouped_artifacts(): + artifacts = normalize_artifacts( + [ + TextArtifact(payload="x", title="Instance 1"), + TextArtifact(payload="y", title="Instance 2"), + ], + create_grouped=True, + ) + assert artifacts == [ + { + "type": "grouped", + "title": None, + "groups": [ + { + "title": "Instance 1", + "artifacts": [ + { + "type": "text", + "payload": "x", + "title": "Instance 1", + "role": "explanation", + "index": 0, + } + ], + }, + { + "title": "Instance 2", + "artifacts": [ + { + "type": "text", + "payload": "y", + "title": "Instance 2", + "role": "explanation", + "index": 1, + } + ], + }, + ], + } + ] + + +def test_artifact_role_defaults_to_explanation(): + from DashAI.back.core.artifacts import TextArtifact + + artifact = TextArtifact(payload="hi") + assert artifact.to_dict()["role"] == "explanation" + + +def test_artifact_role_roundtrips_input(): + from DashAI.back.core.artifacts import TableArtifact, TablePayload + + artifact = TableArtifact( + payload=TablePayload(columns=["a"], rows=[[1]]), + role="input", + ) + assert artifact.to_dict()["role"] == "input" + + +def test_normalize_artifacts_preserves_role(): + from DashAI.back.core.artifacts import normalize_artifacts + + result = normalize_artifacts( + [{"type": "text", "payload": "x", "title": "Instance 1", "role": "input"}] + ) + assert result[0]["role"] == "input" + + +def test_build_tabular_input_artifact(): + from DashAI.back.core.artifacts import build_tabular_input_artifact + + artifact = build_tabular_input_artifact(["age", "city"], [42, "NY"], "Instance 1") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["title"] == "Instance 1" + assert payload["payload"]["columns"] == ["age", "city"] + assert payload["payload"]["rows"] == [[42, "NY"]] + + +def test_build_text_input_artifact(): + from DashAI.back.core.artifacts import build_text_input_artifact + + artifact = build_text_input_artifact("hello world", "Instance 2") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["type"] == "text" + assert payload["payload"] == "hello world" + + +def test_build_image_input_artifact(): + from DashAI.back.core.artifacts import build_image_input_artifact + from DashAI.back.types.dashai_image import DashAIImage + + image = DashAIImage(bytes=PNG_BYTES, path="img.png") + artifact = build_image_input_artifact(image, "Instance 3") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["type"] == "image" + assert payload["title"] == "Instance 3" diff --git a/tests/back/dependencies/test_nested_downloads.py b/tests/back/dependencies/test_nested_downloads.py new file mode 100644 index 000000000..9b196566f --- /dev/null +++ b/tests/back/dependencies/test_nested_downloads.py @@ -0,0 +1,103 @@ +"""Tests for nested downloadable-component discovery.""" + +from DashAI.back.dependencies.downloads.nested import ( + iter_config_components, + missing_downloads, +) + + +class _Comp: + """Stand-in component class carrying only download-related attributes.""" + + def __init__(self, requires, size=None): + self.REQUIRES_DOWNLOAD = requires + self.DOWNLOAD_SIZE_BYTES = size + + +class _FakeRegistry: + """Minimal registry: maps names to component classes and download state.""" + + def __init__(self, classes, downloaded): + self._classes = classes + self._downloaded = downloaded + + def __contains__(self, name): + return name in self._classes + + def __getitem__(self, name): + return {"class": self._classes[name]} + + def refresh_download_status(self, name): + return self._downloaded.get(name, True) + + +def test_iter_flat_component(): + params = {"tabular_classifier": {"component": "SVC", "params": {}}} + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_unwraps_properties(): + params = { + "tabular_classifier": { + "properties": {"component": "SVC", "params": {}}, + } + } + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_comp_wrapper(): + params = { + "tabular_classifier": { + "component": "BagOfWords", + "params": {"comp": {"component": "SVC", "params": {}}}, + } + } + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_nested_depth(): + params = { + "outer": { + "component": "Wrapper", + "params": {"inner": {"component": "SVC", "params": {}}}, + } + } + names = list(iter_config_components(params)) + assert names == [("Wrapper", None), ("SVC", "Wrapper")] + + +def test_iter_ignores_primitives_and_fixed_values(): + params = {"n": 5, "alpha": {"fixed_value": 0.1}} + assert list(iter_config_components(params)) == [] + + +def test_missing_downloads_reports_undownloaded(): + classes = { + "SVC": _Comp(requires=False), + "BigNet": _Comp(requires=True, size=42), + } + downloaded = {"BigNet": False} + reg = _FakeRegistry(classes, downloaded) + params = { + "a": {"component": "SVC", "params": {}}, + "b": {"component": "BigNet", "params": {}}, + } + missing = missing_downloads(params, reg) + assert missing == [{"name": "BigNet", "parent": None, "download_size_bytes": 42}] + + +def test_missing_downloads_empty_when_all_present(): + classes = {"BigNet": _Comp(requires=True, size=1)} + reg = _FakeRegistry(classes, {"BigNet": True}) + params = {"b": {"component": "BigNet", "params": {}}} + assert missing_downloads(params, reg) == [] + + +def test_missing_downloads_dedupes(): + classes = {"BigNet": _Comp(requires=True, size=1)} + reg = _FakeRegistry(classes, {"BigNet": False}) + params = { + "a": {"component": "BigNet", "params": {}}, + "b": {"component": "BigNet", "params": {}}, + } + assert len(missing_downloads(params, reg)) == 1 diff --git a/tests/back/downloads/__init__.py b/tests/back/downloads/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/back/downloads/test_component_path.py b/tests/back/downloads/test_component_path.py new file mode 100644 index 000000000..765dc9f35 --- /dev/null +++ b/tests/back/downloads/test_component_path.py @@ -0,0 +1,11 @@ +from DashAI.back.config import DefaultSettings +from DashAI.back.dependencies.config_builder import build_config_dict + + +def test_default_settings_has_component_path(): + assert DefaultSettings().COMPONENT_PATH == "components" + + +def test_component_path_resolved_under_local_path(tmp_path): + config = build_config_dict(local_path=tmp_path, logging_level="INFO") + assert config["COMPONENT_PATH"] == tmp_path / "components" diff --git a/tests/back/downloads/test_downloadable.py b/tests/back/downloads/test_downloadable.py new file mode 100644 index 000000000..4597dc2a3 --- /dev/null +++ b/tests/back/downloads/test_downloadable.py @@ -0,0 +1,117 @@ +import pathlib +from unittest import mock + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads import downloadable as dl + +_SENTINEL = object() + + +@pytest.fixture +def components_root(tmp_path): + # kink Container has no .get(), so monkeypatch.setitem is not usable. + # Save and restore the "config" key manually. + try: + old = di["config"] + except KeyError: + old = _SENTINEL + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield pathlib.Path(tmp_path) + if old is _SENTINEL: + del di["config"] + else: + di["config"] = old + + +class _Dummy(dl.HFDownloadableMixin): + HF_REPOS = [("owner/model-a", "model")] + + +def _populate(root: pathlib.Path, cls, repo_leaf: str): + d = root / cls.__name__ / repo_leaf + d.mkdir(parents=True) + (d / "config.json").write_text("{}") + + +def test_component_dir_under_root(components_root): + assert _Dummy.component_dir() == components_root / "_Dummy" + + +def test_is_downloaded_false_when_absent(components_root): + assert _Dummy.is_downloaded() is False + + +def test_is_downloaded_true_when_present(components_root): + _populate(components_root, _Dummy, "model-a") + assert _Dummy.is_downloaded() is True + + +def test_is_downloaded_false_when_no_repos(components_root): + class Empty(dl.HFDownloadableMixin): + HF_REPOS = [] + + assert Empty.is_downloaded() is False + + +def test_download_fetches_into_component_dir(components_root): + calls = [] + with mock.patch.object(dl, "snapshot_download") as snap: + _Dummy.download(lambda frac, msg: calls.append((frac, msg))) + snap.assert_called_once_with( + repo_id="owner/model-a", + repo_type="model", + local_dir=str(components_root / "_Dummy" / "model-a"), + ignore_patterns=list(_Dummy.HF_IGNORE_PATTERNS), + ) + assert calls[0] == (None, "Downloading owner/model-a") + + +def test_delete_removes_component_dir(components_root): + _populate(components_root, _Dummy, "model-a") + _Dummy.delete() + assert not (components_root / "_Dummy").exists() + + +# --------------------------------------------------------------------------- +# 3-tuple (allow_patterns) support +# --------------------------------------------------------------------------- + + +class _DummyPartial(dl.HFDownloadableMixin): + HF_REPOS = [("owner/model-a", "model", ["*8_0.gguf"])] + + +def test_download_3tuple_passes_allow_patterns(components_root): + with mock.patch.object(dl, "snapshot_download") as snap: + _DummyPartial.download(lambda frac, msg: None) + snap.assert_called_once_with( + repo_id="owner/model-a", + repo_type="model", + local_dir=str(components_root / "_DummyPartial" / "model-a"), + allow_patterns=["*8_0.gguf"], + ignore_patterns=list(_DummyPartial.HF_IGNORE_PATTERNS), + ) + + +def test_download_2tuple_no_allow_patterns(components_root): + with mock.patch.object(dl, "snapshot_download") as snap: + _Dummy.download(lambda frac, msg: None) + _call_kwargs = snap.call_args.kwargs + assert "allow_patterns" not in _call_kwargs + + +def test_is_downloaded_3tuple_true_when_present(components_root): + _populate(components_root, _DummyPartial, "model-a") + assert _DummyPartial.is_downloaded() is True + + +def test_is_downloaded_3tuple_false_when_absent(components_root): + assert _DummyPartial.is_downloaded() is False + + +def test_is_downloaded_3tuple_false_when_empty_dir(components_root): + d = components_root / "_DummyPartial" / "model-a" + d.mkdir(parents=True) + assert _DummyPartial.is_downloaded() is False diff --git a/tests/back/explainers/test_explainers.py b/tests/back/explainers/test_explainers.py index 86408cc18..2c854683a 100644 --- a/tests/back/explainers/test_explainers.py +++ b/tests/back/explainers/test_explainers.py @@ -1,9 +1,11 @@ import copy +import json import pyarrow as pa import pytest from datasets import DatasetDict +from DashAI.back.core.artifacts import GroupedArtifacts, PlotlyArtifact from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader from DashAI.back.dataloaders.classes.dashai_dataset import ( DashAIDataset, @@ -128,7 +130,20 @@ def test_partial_dependence(trained_model: BaseModel, dataset): assert set(metadata["target_names"]) == set(TARGETS) assert len(explanation) == len(INPUT_COLUMNS) + # A single grouped artifact with one group (a plotly curve) per feature and + # class, so the frontend renders a selector instead of an in-figure dropdown. assert len(plot) == 1 + grouped = plot[0] + assert isinstance(grouped, GroupedArtifacts) + assert len(grouped.groups) == len(INPUT_COLUMNS) * len(TARGETS) + for group in grouped.groups: + assert group.title.startswith("Feature: ") + assert len(group.artifacts) == 1 + artifact = group.artifacts[0] + assert isinstance(artifact, PlotlyArtifact) + artifact_dict = artifact.to_dict() + assert artifact_dict["type"] == "plotly" + json.loads(artifact_dict["payload"]) for feature_key in explanation.values(): assert "grid_values" in feature_key @@ -163,7 +178,21 @@ def test_permutation_feature_importance(trained_model: BaseModel, dataset: Datas key in explanation for key in ["features", "importances_mean", "importances_std"] ) + # A single grouped artifact with one group (a bar chart) per feature count + # (all features down to one), so the frontend lists the counts in a selector + # instead of an in-figure dropdown. assert len(plot) == 1 + grouped = plot[0] + assert isinstance(grouped, GroupedArtifacts) + assert len(grouped.groups) == len(INPUT_COLUMNS) + for group in grouped.groups: + assert group.title.startswith("Top ") + assert len(group.artifacts) == 1 + artifact = group.artifacts[0] + assert isinstance(artifact, PlotlyArtifact) + artifact_dict = artifact.to_dict() + assert artifact_dict["type"] == "plotly" + json.loads(artifact_dict["payload"]) for values in explanation.values(): assert len(values) == len(INPUT_COLUMNS) @@ -183,6 +212,7 @@ def test_permutation_feature_importance(trained_model: BaseModel, dataset: Datas for key in ["features", "importances_mean", "importances_std"] ) assert len(plot) == 1 + assert len(plot[0].groups) == len(INPUT_COLUMNS) for values in explanation.values(): assert len(values) == len(INPUT_COLUMNS) diff --git a/tests/back/explainers/test_image_explainers.py b/tests/back/explainers/test_image_explainers.py new file mode 100644 index 000000000..88bc341b1 --- /dev/null +++ b/tests/back/explainers/test_image_explainers.py @@ -0,0 +1,158 @@ +import numpy as np +import pytest +from PIL import Image + +from DashAI.back.explainability.explainers.grad_cam import GradCam +from DashAI.back.explainability.explainers.occlusion_saliency import ( + OcclusionSaliency, +) +from DashAI.back.models.image_explainable_model import GradCamCompatibleModel + +IMAGE_SIZE = 32 + + +class _FakeImageValue: + """Wraps a PIL image behind the DashAI image type interface.""" + + def __init__(self, pil_image): + self._pil_image = pil_image + + def to_pil(self): + return self._pil_image + + +class _FakeImageDataset: + """Minimal stand-in for a DashAIDataset holding one image column.""" + + def __init__(self, images): + self._rows = [{"image": _FakeImageValue(image)} for image in images] + self.features = {"image": None} + + def __len__(self): + return len(self._rows) + + def __getitem__(self, index): + return self._rows[index] + + +class _ConvImageModel(GradCamCompatibleModel): + """Tiny convolutional image classifier exposing the capability contract.""" + + def __init__(self): + import torch + import torch.nn as nn + + torch.manual_seed(0) + self.image_size = IMAGE_SIZE + self.device = torch.device("cpu") + self.idx_to_label = {0: "cat", 1: "dog"} + self.model = nn.Sequential( + nn.Conv2d(3, 4, 3, padding=1), + nn.ReLU(), + nn.AdaptiveAvgPool2d(4), + nn.Flatten(), + nn.Linear(4 * 4 * 4, 2), + ) + + def get_inference_transform(self): + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + ] + ) + + +class _MlpImageModel(_ConvImageModel): + """Image model with no convolutional layers (like MLPImageClassifier).""" + + def __init__(self): + import torch + import torch.nn as nn + + super().__init__() + torch.manual_seed(0) + self.model = nn.Sequential( + nn.Flatten(), + nn.Linear(3 * IMAGE_SIZE * IMAGE_SIZE, 2), + ) + + +@pytest.fixture(name="images") +def images_fixture(): + rng = np.random.RandomState(0) + return [ + Image.fromarray( + rng.randint(0, 255, size=(IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8) + ) + for _ in range(2) + ] + + +def _assert_image_explanation(explanation, n_instances): + assert explanation["metadata"]["target_names"] == ["cat", "dog"] + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == n_instances + + for key in instance_keys: + instance = explanation[key] + heatmap = np.asarray(instance["heatmap"]) + assert heatmap.shape == (IMAGE_SIZE, IMAGE_SIZE) + assert heatmap.min() >= 0.0 + assert heatmap.max() <= 1.0 + assert np.asarray(instance["image"]).shape == (IMAGE_SIZE, IMAGE_SIZE, 3) + assert instance["predicted_class"] in (0, 1) + assert len(instance["model_prediction"]) == 2 + + +@pytest.mark.parametrize("method", ["gradcam", "gradcam++"]) +def test_grad_cam(images, method): + model = _ConvImageModel() + explainer = GradCam(model, method=method) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(images) + for group in groups: + assert [a.type for a in group.artifacts] == ["plotly", "text"] + + +def test_grad_cam_rejects_non_convolutional_models(images): + explainer = GradCam(_MlpImageModel()) + explainer.fit((None, None)) + + with pytest.raises(ValueError, match="convolutional"): + explainer.explain_instance(_FakeImageDataset(images)) + + +def test_occlusion_saliency(images): + model = _ConvImageModel() + explainer = OcclusionSaliency(model, patch_size=8, stride=8) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(images) + for group in groups: + assert [a.type for a in group.artifacts] == ["plotly", "text"] + + +def test_occlusion_saliency_works_without_conv_layers(images): + # Unlike Grad-CAM, occlusion only needs forward passes. + explainer = OcclusionSaliency(_MlpImageModel(), patch_size=8, stride=8) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) diff --git a/tests/back/explainers/test_lib_explainers.py b/tests/back/explainers/test_lib_explainers.py new file mode 100644 index 000000000..acd8cf9de --- /dev/null +++ b/tests/back/explainers/test_lib_explainers.py @@ -0,0 +1,179 @@ +import copy + +import numpy as np +import pandas as pd +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.dice_counterfactual import ( + DiceCounterfactual, +) +from DashAI.back.explainability.explainers.lime_text import LimeText +from DashAI.back.models.scikit_learn.decision_tree_classifier import ( + DecisionTreeClassifier, +) +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", + "PetalWidthCm", +] +OUTPUT_COLUMNS = ["Species"] +TARGETS = [ + "Iris-setosa", + "Iris-versicolor", + "Iris-virginica", +] + + +@pytest.fixture(scope="module", name="dataset") +def tabular_dataset_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical(values=TARGETS), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns(split_dataset_dict, INPUT_COLUMNS, OUTPUT_COLUMNS) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_model") +def trained_model(dataset): + x, y = dataset + model = DecisionTreeClassifier( + criterion="gini", + max_depth=3, + min_samples_split=2, + min_samples_leaf=1, + max_features=None, + ) + model.train(x["train"], y["train"]) + + return model + + +def test_dice_counterfactual(trained_model, dataset): + x, _ = dataset + + explainer = DiceCounterfactual(trained_model, total_cfs=2, method="random") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert metadata["feature_names"] == INPUT_COLUMNS + assert set(metadata["target_names"]) == set(TARGETS) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 2 + + found_any = False + for key in instance_keys: + instance = explanation[key] + assert len(instance["instance_values"]) == len(INPUT_COLUMNS) + assert 0 <= instance["predicted_class"] < len(TARGETS) + for counterfactual in instance["counterfactuals"]: + found_any = True + assert len(counterfactual["values"]) == len(INPUT_COLUMNS) + # A counterfactual must reach a different class. + assert counterfactual["predicted_class"] != instance["predicted_class"] + # DiCE's random search on iris should find counterfactuals. + assert found_any + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(instance_keys) + for group in groups: + assert [a.type for a in group.artifacts] == ["table", "text"] + + +class DummyTextModel: + """Predicts positive when the text contains the word 'good'.""" + + def predict(self, dataset): + frame = dataset.to_pandas() + texts = frame.iloc[:, 0].tolist() + return np.array( + [[0.1, 0.9] if "good" in str(t).split() else [0.9, 0.1] for t in texts] + ) + + +def test_lime_text(): + explainer = LimeText(DummyTextModel(), num_features=5, num_samples=200) + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame({"text": ["this movie was good indeed"]}) + explanation = explainer.explain_instance(instances) + + instance = explanation[0] + assert instance["predicted_class"] == 1 + + word_weights = dict(instance["word_weights"]) + assert "good" in word_weights + # 'good' drives the dummy model towards the positive class. + assert word_weights["good"] > 0 + assert word_weights["good"] == max(word_weights.values()) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == 1 + assert [a.type for a in groups[0].artifacts] == ["plotly", "text"] + assert "good" in groups[0].artifacts[1].payload diff --git a/tests/back/explainers/test_new_explainers.py b/tests/back/explainers/test_new_explainers.py new file mode 100644 index 000000000..775a59366 --- /dev/null +++ b/tests/back/explainers/test_new_explainers.py @@ -0,0 +1,228 @@ +import copy + +import numpy as np +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.contrastive_shap import ContrastiveShap +from DashAI.back.explainability.explainers.nearest_counterfactual import ( + NearestCounterfactual, +) +from DashAI.back.models.scikit_learn.decision_tree_classifier import ( + DecisionTreeClassifier, +) +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", + "PetalWidthCm", +] +OUTPUT_COLUMNS = ["Species"] +TARGETS = [ + "Iris-setosa", + "Iris-versicolor", + "Iris-virginica", +] + + +@pytest.fixture(scope="module", name="dataset") +def tabular_model_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical(values=TARGETS), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns(split_dataset_dict, INPUT_COLUMNS, OUTPUT_COLUMNS) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_model") +def trained_model(dataset): + x, y = dataset + model = DecisionTreeClassifier( + criterion="gini", + max_depth=3, + min_samples_split=2, + min_samples_leaf=1, + max_features=None, + ) + model.train(x["train"], y["train"]) + + return model + + +def test_nearest_counterfactual(trained_model, dataset): + x, _ = dataset + n_counterfactuals = 2 + + explainer = NearestCounterfactual( + trained_model, n_counterfactuals=n_counterfactuals, distance="l1" + ) + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"] + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert set(metadata["target_names"]) == set(TARGETS) + assert metadata["feature_names"] == INPUT_COLUMNS + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == instances.num_rows + + for key in instance_keys: + instance = explanation[key] + assert len(instance["instance_values"]) == len(INPUT_COLUMNS) + assert len(instance["model_prediction"]) == len(TARGETS) + assert len(instance["counterfactuals"]) <= n_counterfactuals + + for counterfactual in instance["counterfactuals"]: + # A counterfactual must be classified differently. + assert counterfactual["predicted_class"] != instance["predicted_class"] + assert counterfactual["distance"] >= 0 + assert len(counterfactual["values"]) == len(INPUT_COLUMNS) + + plot = explainer.plot(explanation) + # A single grouped artifact with one group per instance, each holding a + # table and a text artifact. + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(instance_keys) + for group in groups: + assert [a.type for a in group.artifacts] == ["table", "text"] + + first_table = groups[0].artifacts[0].payload + # Feature rows plus the predicted class row. + assert len(first_table.rows) == len(INPUT_COLUMNS) + 1 + for cell in first_table.highlight: + assert 0 <= cell.row < len(first_table.rows) + assert 0 <= cell.column < len(first_table.columns) + + +def test_nearest_counterfactual_distance_l2(trained_model, dataset): + x, _ = dataset + + explainer = NearestCounterfactual(trained_model, n_counterfactuals=1, distance="l2") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 2 + for key in instance_keys: + assert len(explanation[key]["counterfactuals"]) == 1 + + +def test_contrastive_shap(trained_model, dataset): + x, _ = dataset + + explainer = ContrastiveShap(trained_model) + explainer.fit( + copy.deepcopy(dataset), + sample_background_data=True, + background_fraction=0.3, + ) + + instances = x["test"].select(range(3)) + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert set(metadata["target_names"]) == set(TARGETS) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 3 + + for key in instance_keys: + instance = explanation[key] + assert instance["fact_class"] != instance["foil_class"] + assert len(instance["delta_values"]) == len(INPUT_COLUMNS) + + delta = np.asarray(instance["delta_values"]) + fact = np.asarray(instance["fact_shap_values"]) + foil = np.asarray(instance["foil_shap_values"]) + assert np.allclose(delta, fact - foil, atol=1e-2) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(instance_keys) + assert [a.type for a in groups[0].artifacts] == ["plotly", "text"] + assert "rather than" in groups[0].artifacts[1].payload + + +def test_contrastive_shap_fixed_foil(trained_model, dataset): + x, _ = dataset + + explainer = ContrastiveShap(trained_model, foil_class="Iris-virginica") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + target_names = explanation["metadata"]["target_names"] + virginica = target_names.index("Iris-virginica") + + instance_keys = [key for key in explanation if key != "metadata"] + for key in instance_keys: + instance = explanation[key] + if instance["fact_class"] != virginica: + assert instance["foil_class"] == virginica + else: + # Fixed foil equals the fact: falls back to the runner-up class. + assert instance["foil_class"] != virginica diff --git a/tests/back/explainers/test_task_explainers.py b/tests/back/explainers/test_task_explainers.py new file mode 100644 index 000000000..c656efc59 --- /dev/null +++ b/tests/back/explainers/test_task_explainers.py @@ -0,0 +1,312 @@ +import copy + +import numpy as np +import pandas as pd +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.regression_kernel_shap import ( + RegressionKernelShap, +) +from DashAI.back.explainability.explainers.regression_partial_dependence import ( + RegressionPartialDependence, +) +from DashAI.back.explainability.explainers.regression_permutation_feature_importance import ( # noqa: E501 + RegressionPermutationFeatureImportance, +) +from DashAI.back.explainability.explainers.token_ablation import TokenAblation +from DashAI.back.models.scikit_learn.linear_regression import LinearRegression +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +REGRESSION_INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", +] +REGRESSION_OUTPUT_COLUMNS = ["PetalWidthCm"] + + +@pytest.fixture(scope="module", name="regression_dataset") +def regression_dataset_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical( + values=["Iris-setosa", "Iris-versicolor", "Iris-virginica"] + ), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns( + split_dataset_dict, REGRESSION_INPUT_COLUMNS, REGRESSION_OUTPUT_COLUMNS + ) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_regressor") +def trained_regressor(regression_dataset): + x, y = regression_dataset + model = LinearRegression(fit_intercept=True) + model.train(x["train"], y["train"]) + + return model + + +def test_regression_permutation_feature_importance( + trained_regressor, regression_dataset +): + explainer = RegressionPermutationFeatureImportance( + trained_regressor, + scoring="r2", + n_repeats=5, + random_state=0, + max_samples_fraction=1.0, + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + + assert explanation["features"] == REGRESSION_INPUT_COLUMNS + assert len(explanation["importances_mean"]) == len(REGRESSION_INPUT_COLUMNS) + assert len(explanation["importances_std"]) == len(REGRESSION_INPUT_COLUMNS) + # PetalLengthCm is highly correlated with PetalWidthCm: its importance + # must be positive. + petal_length = explanation["features"].index("PetalLengthCm") + assert explanation["importances_mean"][petal_length] > 0 + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 1 + assert artifacts[0].type == "plotly" + assert artifacts[0].title == "Permutation Feature Importance" + + +@pytest.mark.parametrize( + "scoring", ["neg_mean_squared_error", "neg_mean_absolute_error"] +) +def test_regression_pfi_other_scorings(trained_regressor, regression_dataset, scoring): + explainer = RegressionPermutationFeatureImportance( + trained_regressor, scoring=scoring, n_repeats=3, random_state=0 + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + assert len(explanation["importances_mean"]) == len(REGRESSION_INPUT_COLUMNS) + + +def test_regression_kernel_shap(trained_regressor, regression_dataset): + x, _ = regression_dataset + + explainer = RegressionKernelShap(trained_regressor) + explainer.fit( + copy.deepcopy(regression_dataset), + sample_background_data=True, + background_fraction=0.3, + ) + + instances = x["test"].select(range(3)) + explanation = explainer.explain_instance(instances) + + assert explanation["metadata"]["feature_names"] == REGRESSION_INPUT_COLUMNS + assert explanation["metadata"]["output_column"] == "PetalWidthCm" + + base_value = explanation["base_value"] + instance_keys = [ + key for key in explanation if key not in ("metadata", "base_value") + ] + assert len(instance_keys) == 3 + + for key in instance_keys: + instance = explanation[key] + assert len(instance["shap_values"]) == len(REGRESSION_INPUT_COLUMNS) + # SHAP values are additive: base + contributions ~= prediction. + reconstructed = base_value + sum(instance["shap_values"]) + assert reconstructed == pytest.approx(instance["model_prediction"], abs=0.05) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(instance_keys) + assert [a.type for a in groups[0].artifacts] == ["plotly", "text"] + assert "baseline" in groups[0].artifacts[1].payload + + +def test_regression_partial_dependence(trained_regressor, regression_dataset): + explainer = RegressionPartialDependence( + trained_regressor, + grid_resolution=10, + lower_percentile=0.05, + upper_percentile=0.95, + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + + assert explanation["metadata"]["output_column"] == "PetalWidthCm" + for feature in REGRESSION_INPUT_COLUMNS: + assert len(explanation[feature]["grid_values"]) == 10 + assert len(explanation[feature]["average"]) == 10 + grid = explanation[feature]["grid_values"] + assert grid == sorted(grid) + + # PetalLengthCm drives PetalWidthCm: its PDP curve must not be flat. + petal_curve = explanation["PetalLengthCm"]["average"] + assert max(petal_curve) - min(petal_curve) > 0.1 + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == len(REGRESSION_INPUT_COLUMNS) + assert all(g.artifacts[0].type == "plotly" for g in groups) + assert groups[0].title in REGRESSION_INPUT_COLUMNS + + +def test_regression_pdp_invalid_percentiles(trained_regressor): + with pytest.raises(AssertionError): + RegressionPartialDependence( + trained_regressor, lower_percentile=0.9, upper_percentile=0.1 + ) + + +class DummyTextModel: + """Predicts positive when the text contains the word 'good'. + + Mimics the transformer models' strictness: predict raises if the dataset + has more than one text column (see ``tokenize_data`` in + ``base_text_classification_transformer``). + """ + + def predict(self, dataset): + frame = dataset.to_pandas() + text_columns = [col for col in frame.columns if col != "label"] + if len(text_columns) != 1: + raise ValueError(f"Expected exactly one text column, found: {text_columns}") + texts = frame[text_columns[0]].tolist() + return np.array( + [[0.1, 0.9] if "good" in str(t).split() else [0.9, 0.1] for t in texts] + ) + + +class _FakeTargetSplit: + """Minimal stand-in for a DashAIDataset target split.""" + + column_names = ["label"] + types = {"label": Categorical(values=["negative", "positive"])} + + +def test_token_ablation_fit_reads_target_names(): + explainer = TokenAblation(DummyTextModel()) + explainer.fit((None, {"train": _FakeTargetSplit()})) + assert explainer.metadata["target_names"] == ["negative", "positive"] + + +def test_token_ablation_explains_influential_tokens(): + explainer = TokenAblation(DummyTextModel(), max_tokens=20, replacement="remove") + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame( + {"text": ["this movie was good indeed", "terrible boring plot"]} + ) + explanation = explainer.explain_instance(instances) + + first = explanation[0] + assert first["predicted_class"] == 1 + tokens = first["tokens"] + importances = first["token_importances"] + assert len(tokens) == len(importances) + + # Removing 'good' flips the dummy model: it must be the top token. + good_importance = importances[tokens.index("good")] + assert good_importance == pytest.approx(0.8, abs=1e-6) + assert all(importance <= good_importance for importance in importances) + + second = explanation[1] + assert second["predicted_class"] == 0 + # No single token changes the dummy model's negative prediction. + assert all( + importance == pytest.approx(0.0, abs=1e-6) + for importance in second["token_importances"] + ) + + plot = explainer.plot(explanation) + assert len(plot) == 1 + groups = plot[0].groups + assert len(groups) == 2 + assert [a.type for a in groups[0].artifacts] == ["plotly", "text"] + assert "good" in groups[0].artifacts[1].payload + + +def test_token_ablation_ignores_tokenizer_columns(): + # The explainer job hands over datasets already prepared by the model; + # transformer models add input_ids/attention_mask columns. The explainer + # must rebuild a clean single text-column dataset before predicting. + explainer = TokenAblation(DummyTextModel(), max_tokens=10) + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame( + { + "text": ["good stuff", "bad stuff"], + "input_ids": [[101, 102], [101, 103]], + "attention_mask": [[1, 1], [1, 1]], + } + ) + explanation = explainer.explain_instance(instances) + + assert explanation["metadata"]["text_column"] == "text" + assert explanation[0]["predicted_class"] == 1 + assert explanation[1]["predicted_class"] == 0 + + +def test_token_ablation_unk_replacement(): + explainer = TokenAblation(DummyTextModel(), max_tokens=10, replacement="unk") + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame({"text": ["good"]}) + explanation = explainer.explain_instance(instances) + + # Single token replaced by [UNK]: prediction flips, importance 0.8. + assert explanation[0]["token_importances"] == [pytest.approx(0.8, abs=1e-6)] diff --git a/tests/back/job_queue/test_component_download_job.py b/tests/back/job_queue/test_component_download_job.py new file mode 100644 index 000000000..026c0fa91 --- /dev/null +++ b/tests/back/job_queue/test_component_download_job.py @@ -0,0 +1,35 @@ +import pytest +from kink import di + +from DashAI.back.job.component_download_job import ComponentDownloadJob + + +@pytest.fixture(autouse=False) +def fake_registry(): + """Inject a minimal component_registry into the kink container.""" + + class FakeComponent: + REQUIRES_DOWNLOAD = True + + calls = {"download": 0} + + @classmethod + def download(cls, report=None): + cls.calls["download"] += 1 + report(None, "Downloading") + + registry = {"FakeComponent": {"class": FakeComponent}} + di["component_registry"] = registry + yield FakeComponent + del di["component_registry"] + + +def test_run_downloads_component(fake_registry): + job = ComponentDownloadJob(component_name="FakeComponent") + job.run() + assert fake_registry.calls["download"] == 1 + + +def test_get_job_name_uses_component_name(): + job = ComponentDownloadJob(component_name="FakeComponent") + assert "FakeComponent" in job.get_job_name() diff --git a/tests/back/job_queue/test_huey_job_queue.py b/tests/back/job_queue/test_huey_job_queue.py index 4879b1b97..67a5c8956 100644 --- a/tests/back/job_queue/test_huey_job_queue.py +++ b/tests/back/job_queue/test_huey_job_queue.py @@ -1,3 +1,4 @@ +import sqlite3 import time # noqa: F401 import pytest @@ -110,3 +111,81 @@ def test_peek_and_get_nonexistent(test_job_queue: HueyJobQueue): with pytest.raises(JobQueueError): test_job_queue.get(job_id) + + +def test_completion_sets_progress_to_100(test_job_queue: HueyJobQueue): + job = DummyJob() + job_id = test_job_queue.put(job).id + + status = test_job_queue.status(job_id) + assert status["status"] == "finished" + assert status["progress"] == 100 + + +def test_report_progress_updates_status(test_job_queue: HueyJobQueue): + job = DummyJob() + job_id = test_job_queue.put(job).id + + test_job_queue.report_progress(job_id, 42.0, "Halfway there") + + status = test_job_queue.status(job_id) + assert status["progress"] == 42.0 + assert status["progress_message"] == "Halfway there" + + +def test_report_progress_none_is_indeterminate(test_job_queue: HueyJobQueue): + job = DummyJob() + job_id = test_job_queue.put(job).id + + test_job_queue.report_progress(job_id, None, "Working") + + status = test_job_queue.status(job_id) + assert status["progress"] is None + assert status["progress_message"] == "Working" + + +def test_report_progress_surfaces_in_changes(test_job_queue: HueyJobQueue): + job = DummyJob() + job_id = test_job_queue.put(job).id + + test_job_queue.report_progress(job_id, 25.0, "Quarter") + + changes = test_job_queue.changes_since("1970-01-01 00:00:00.000000") + changed = next(j for j in changes if j["id"] == job_id) + assert changed["progress"] == 25.0 + assert changed["progress_message"] == "Quarter" + + +def test_ensure_progress_columns_migrates_old_table(tmp_path): + # Simulate an install created before progress tracking: a 'task_copy' + # table without the progress columns. + db_path = tmp_path / "legacy.db" + with sqlite3.connect(db_path) as conn: + conn.execute( + """ + CREATE TABLE task_copy ( + id TEXT PRIMARY KEY, + task_type TEXT NOT NULL, + job_name TEXT, + enqueued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + status TEXT NOT NULL, + last_update DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + error_msg TEXT + ) + """ + ) + + # Constructing the queue against the same directory must add the columns. + HueyJobQueue("legacy", path_db=str(tmp_path)) + + with sqlite3.connect(db_path) as conn: + cols = {row[1] for row in conn.execute("PRAGMA table_info(task_copy)")} + assert "progress" in cols + assert "progress_message" in cols + + +def test_base_job_report_progress_noop_without_huey_id(): + # A job with no huey_id (e.g. immediate mode) must not raise. + job = DummyJob() + assert job.kwargs.get("huey_id") is None + job.report_progress(0.5, "Should be ignored") diff --git a/tests/back/metrics/test_classification_metrics.py b/tests/back/metrics/test_classification_metrics.py index 03f667812..b95f8d964 100644 --- a/tests/back/metrics/test_classification_metrics.py +++ b/tests/back/metrics/test_classification_metrics.py @@ -5,7 +5,9 @@ from datasets import Dataset from DashAI.back.metrics.classification.accuracy import Accuracy +from DashAI.back.metrics.classification.balanced_accuracy import BalancedAccuracy from DashAI.back.metrics.classification.f1 import F1 +from DashAI.back.metrics.classification.matthews_corrcoef import MatthewsCorrCoef from DashAI.back.metrics.classification.precision import Precision from DashAI.back.metrics.classification.recall import Recall @@ -27,6 +29,32 @@ def test_accuracy(metric_input: Dict[str, List[int]]): assert score <= 1.0 +def test_balanced_accuracy(metric_input: Dict[str, List[int]]): + score = BalancedAccuracy.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert isinstance(score, float) + assert score >= 0.0 + assert score <= 1.0 + + +def test_balanced_accuracy_matches_sklearn_reference( + metric_input: Dict[str, List[int]], +): + from sklearn.metrics import balanced_accuracy_score + + true = np.array(metric_input["true_labels"]["foo"]) + pred = np.argmax(metric_input["pred_labels"], axis=1) + expected = balanced_accuracy_score(true, pred) + + score = BalancedAccuracy.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert score == pytest.approx(expected) + + def test_precision(metric_input: Dict[str, List[int]]): score = Precision.score(metric_input["true_labels"], metric_input["pred_labels"]) @@ -51,6 +79,32 @@ def test_f1_score(metric_input: Dict[str, List[int]]): assert score <= 1.0 +def test_matthews_corrcoef(metric_input: Dict[str, List[int]]): + score = MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert isinstance(score, float) + assert score >= -1.0 + assert score <= 1.0 + + +def test_matthews_corrcoef_matches_sklearn_reference( + metric_input: Dict[str, List[int]], +): + from sklearn.metrics import matthews_corrcoef + + true = np.array(metric_input["true_labels"]["foo"]) + pred = np.argmax(metric_input["pred_labels"], axis=1) + expected = matthews_corrcoef(true, pred) + + score = MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert score == pytest.approx(expected) + + def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): error_pattern = ( r"The length of the true labels and the predicted labels must be equal, " @@ -63,6 +117,14 @@ def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): ): Accuracy.score(metric_input["true_labels"], metric_input["wrong_size_labels"]) + with pytest.raises( + ValueError, + match=error_pattern, + ): + BalancedAccuracy.score( + metric_input["true_labels"], metric_input["wrong_size_labels"] + ) + with pytest.raises( ValueError, match=error_pattern, @@ -80,3 +142,11 @@ def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): match=error_pattern, ): F1.score(metric_input["true_labels"], metric_input["wrong_size_labels"]) + + with pytest.raises( + ValueError, + match=error_pattern, + ): + MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["wrong_size_labels"] + ) diff --git a/tests/back/models/test_bart_regression.py b/tests/back/models/test_bart_regression.py new file mode 100644 index 000000000..480f4914b --- /dev/null +++ b/tests/back/models/test_bart_regression.py @@ -0,0 +1,98 @@ +"""Tests for the pymc-bart backed BART regression model.""" + +import numpy as np +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset +from DashAI.back.models.pymc.bart_regression import BARTRegression +from DashAI.back.types.value_types import Float + + +@pytest.fixture(scope="module", name="regression_dataset") +def fixture_regression_dataset(): + """A small synthetic regression dataset with a strong linear signal.""" + import pandas as pd + + rng = np.random.default_rng(0) + n = 120 + x0 = rng.uniform(-2, 2, n) + x1 = rng.uniform(-2, 2, n) + x2 = rng.uniform(-2, 2, n) + y = 3.0 * x0 - 2.0 * x1 + 0.5 * x2 + rng.normal(0, 0.2, n) + + feature_df = pd.DataFrame({"x0": x0, "x1": x1, "x2": x2}).astype("float64") + target_df = pd.DataFrame({"target": y}).astype("float64") + + feature_types = {c: Float(arrow_type=pa.float64()) for c in feature_df.columns} + target_types = {"target": Float(arrow_type=pa.float64())} + + x = to_dashai_dataset(feature_df, types=feature_types) + y_ds = to_dashai_dataset(target_df, types=target_types) + + split = 90 + x_train = to_dashai_dataset(feature_df.iloc[:split], types=feature_types) + x_test = to_dashai_dataset(feature_df.iloc[split:], types=feature_types) + y_train = to_dashai_dataset(target_df.iloc[:split], types=target_types) + + return { + "x": x, + "y": y_ds, + "x_train": x_train, + "x_test": x_test, + "y_train": y_train, + "y_true_test": y[split:], + } + + +@pytest.fixture(scope="module", name="bart_params") +def fixture_bart_params() -> dict: + # Deliberately small so the MCMC test stays fast. + return { + "m": 20, + "alpha": 0.95, + "beta": 2.0, + "response": "constant", + "draws": 100, + "tune": 50, + "chains": 1, + "random_seed": 0, + } + + +def test_bart_get_schema(): + schema = BARTRegression.get_schema() + assert isinstance(schema, dict) + assert schema["type"] == "object" + assert isinstance(schema["properties"], dict) + for key in ("m", "alpha", "beta", "response", "draws", "tune", "chains"): + assert key in schema["properties"], f"missing schema field {key}" + + +def test_bart_train_and_predict(regression_dataset, bart_params): + model = BARTRegression(**bart_params) + model.train(regression_dataset["x_train"], regression_dataset["y_train"]) + + y_pred = model.predict(regression_dataset["x_test"]) + + assert isinstance(y_pred, np.ndarray) + assert y_pred.shape == (regression_dataset["x_test"].num_rows,) + assert np.all(np.isfinite(y_pred)) + + # The signal is strong, so predictions must track the true targets. + corr = np.corrcoef(y_pred, regression_dataset["y_true_test"])[0, 1] + assert corr > 0.8, f"BART predictions poorly correlated with target (corr={corr})" + + +def test_bart_save_and_load(tmp_path, regression_dataset, bart_params): + model = BARTRegression(**bart_params) + model.train(regression_dataset["x_train"], regression_dataset["y_train"]) + y_pred = model.predict(regression_dataset["x_test"]) + + model_path = str(tmp_path / "bart_model.joblib") + model.save(model_path) + loaded = BARTRegression.load(model_path) + y_pred_loaded = loaded.predict(regression_dataset["x_test"]) + + assert isinstance(y_pred_loaded, np.ndarray) + np.testing.assert_allclose(y_pred_loaded, y_pred) diff --git a/tests/back/models/test_base_text_classification_transformer.py b/tests/back/models/test_base_text_classification_transformer.py index 223fdee92..b3d879ced 100644 --- a/tests/back/models/test_base_text_classification_transformer.py +++ b/tests/back/models/test_base_text_classification_transformer.py @@ -4,7 +4,10 @@ class DummyTokenizer: - pass + def save_pretrained(self, save_directory): + save_path = Path(save_directory) + save_path.mkdir(parents=True, exist_ok=True) + (save_path / "tokenizer.json").write_text("{}", encoding="utf-8") class DummyConfig: diff --git a/tests/back/models/test_controlnet_downloadable.py b/tests/back/models/test_controlnet_downloadable.py new file mode 100644 index 000000000..461884509 --- /dev/null +++ b/tests/back/models/test_controlnet_downloadable.py @@ -0,0 +1,94 @@ +"""Tests that ControlNet models expose multi-repo download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.hugging_face.sd15_depth_controlnet_model import ( + SD15DepthControlNetModel, +) +from DashAI.back.models.hugging_face.sd15_hed_controlnet_model import ( + SD15HEDControlNetModel, +) +from DashAI.back.models.hugging_face.sd15_openpose_controlnet_model import ( + SD15OpenPoseControlNetModel, +) +from DashAI.back.models.hugging_face.sdxl_canny_controlnet_model import ( + SDXLCannyControlNetModel, +) +from DashAI.back.models.hugging_face.stable_diffusion_v1_depth_controlnet import ( + StableDiffusionXLV1ControlNet, +) + +_CLASSES = [ + SD15DepthControlNetModel, + SD15HEDControlNetModel, + SD15OpenPoseControlNetModel, + SDXLCannyControlNetModel, + StableDiffusionXLV1ControlNet, +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("model_cls", _CLASSES) +def test_controlnet_is_downloadable(model_cls): + assert issubclass(model_cls, HFDownloadableMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + # Each ControlNet pulls several repos (base + controlnet [+ vae]). + assert len(model_cls.hf_repos()) >= 2 + + +# Preprocessor repos are fetched by download() (not from the Hub at run time), +# so they must be part of hf_repos() for the models that use one. +_PREPROCESSOR_REPOS = { + SD15DepthControlNetModel: "Intel/dpt-hybrid-midas", + SD15HEDControlNetModel: "lllyasviel/Annotators", + SD15OpenPoseControlNetModel: "lllyasviel/Annotators", + StableDiffusionXLV1ControlNet: "Intel/dpt-hybrid-midas", +} + + +@pytest.mark.parametrize(("model_cls", "repo"), _PREPROCESSOR_REPOS.items()) +def test_controlnet_preprocessor_included(model_cls, repo): + assert repo in [rid for rid, *_ in model_cls.hf_repos()] + + +@pytest.mark.parametrize("model_cls", _CLASSES) +def test_controlnet_metadata_flags_download(model_cls): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +def test_controlnet_is_downloaded_requires_all_repos(component_root): + """is_downloaded must be True only when every repo dir is present.""" + model_cls = SD15DepthControlNetModel + repos = [rid for rid, *_ in model_cls.hf_repos()] + + assert model_cls.is_downloaded() is False + + # Only the first repo present -> still not downloaded. + first = component_root / model_cls.__name__ / repos[0].split("/")[-1] + first.mkdir(parents=True) + (first / "config.json").write_text("{}") + assert model_cls.is_downloaded() is False + + # All repos present -> downloaded. + for rid in repos[1:]: + d = component_root / model_cls.__name__ / rid.split("/")[-1] + d.mkdir(parents=True) + (d / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True diff --git a/tests/back/models/test_download_metadata.py b/tests/back/models/test_download_metadata.py new file mode 100644 index 000000000..916df75ad --- /dev/null +++ b/tests/back/models/test_download_metadata.py @@ -0,0 +1,31 @@ +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.base_model import BaseModel + + +class _PlainModel(BaseModel): + def save(self, filename): ... + @classmethod + def load(cls, filename): ... + def train(self, x, y, xv, yv): ... + + +class _DownloadableModel(HFDownloadableMixin, BaseModel): + HF_REPOS = [("owner/model", "model")] + DOWNLOAD_SIZE_BYTES = 1234 + + def save(self, filename): ... + @classmethod + def load(cls, filename): ... + def train(self, x, y, xv, yv): ... + + +def test_plain_model_not_downloadable(): + meta = _PlainModel.get_metadata() + assert meta["requires_download"] is False + assert meta["download_size_bytes"] is None + + +def test_downloadable_model_metadata(): + meta = _DownloadableModel.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == 1234 diff --git a/tests/back/models/test_generative_download_metadata.py b/tests/back/models/test_generative_download_metadata.py new file mode 100644 index 000000000..09ff25d01 --- /dev/null +++ b/tests/back/models/test_generative_download_metadata.py @@ -0,0 +1,35 @@ +"""Tests for BaseGenerativeModel.get_metadata download fields.""" + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.base_generative_model import BaseGenerativeModel + + +class _PlainGenerativeModel(BaseGenerativeModel): + def __init__(self, **kwargs): + pass + + def generate(self, input): + return [] + + +class _DownloadableGenerativeModel(HFDownloadableMixin, BaseGenerativeModel): + HF_REPOS = [("owner/x", "model")] + DOWNLOAD_SIZE_BYTES = 1234 + + def __init__(self, **kwargs): + pass + + def generate(self, input): + return [] + + +def test_plain_generative_model_not_downloadable(): + meta = _PlainGenerativeModel.get_metadata() + assert meta["requires_download"] is False + assert meta["download_size_bytes"] is None + + +def test_downloadable_generative_model_metadata(): + meta = _DownloadableGenerativeModel.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == 1234 diff --git a/tests/back/models/test_gguf_text_generation.py b/tests/back/models/test_gguf_text_generation.py new file mode 100644 index 000000000..714800608 --- /dev/null +++ b/tests/back/models/test_gguf_text_generation.py @@ -0,0 +1,101 @@ +"""Tests for the per-checkpoint GGUF text-generation components.""" + +import pathlib + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.initial_components import get_initial_components +from DashAI.back.models.hugging_face.llama_model import ( + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, +) +from DashAI.back.models.hugging_face.mistral_model import ( + Mistral7BInstructV03, + MistralNemoInstruct2407, +) +from DashAI.back.models.hugging_face.mixtral_model import ( + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, +) +from DashAI.back.models.hugging_face.qwen_model import ( + Qwen25_05BInstruct, + Qwen25_15BInstruct, +) +from DashAI.back.models.hugging_face.smol_lm_model import ( + SmolLM2_17BInstruct, + SmolLM2_360MInstruct, +) + +ALL_CHECKPOINTS = [ + Qwen25_05BInstruct, + Qwen25_15BInstruct, + SmolLM2_360MInstruct, + SmolLM2_17BInstruct, + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, + Mistral7BInstructV03, + MistralNemoInstruct2407, + Mixtral8x7BInstructQ4KM, + Mixtral8x7BInstructQ2K, +] + + +@pytest.fixture +def component_root(tmp_path): + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield pathlib.Path(tmp_path) + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_checkpoint_is_downloadable(cls): + assert issubclass(cls, HFDownloadableMixin) + assert cls.REQUIRES_DOWNLOAD is True + assert cls.DOWNLOAD_SIZE_BYTES is not None + assert cls.REPO_ID + assert cls.GGUF_PATTERN + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_hf_repos_single_file_entry(cls): + assert cls.hf_repos() == [(cls.REPO_ID, "model", [cls.GGUF_PATTERN])] + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_metadata_flags_download(cls): + meta = cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == cls.DOWNLOAD_SIZE_BYTES + + +def test_is_downloaded_reflects_component_dir(component_root): + cls = Qwen25_05BInstruct + assert cls.is_downloaded() is False + + repo_dir = component_root / cls.__name__ / cls.REPO_ID.split("/")[-1] + repo_dir.mkdir(parents=True) + (repo_dir / "model-q8_0.gguf").write_text("weights") + assert cls.is_downloaded() is True + + +def test_new_classes_registered_old_ones_gone(): + registered = {c.__name__ for c in get_initial_components()} + for cls in ALL_CHECKPOINTS: + assert cls.__name__ in registered + for old in { + "QwenModel", + "SmolLMModel", + "LlamaModel", + "MistralModel", + "MixtralModel", + }: + assert old not in registered diff --git a/tests/back/models/test_image_classification_downloadable.py b/tests/back/models/test_image_classification_downloadable.py new file mode 100644 index 000000000..df315d2c1 --- /dev/null +++ b/tests/back/models/test_image_classification_downloadable.py @@ -0,0 +1,54 @@ +"""Tests that torchvision image classifiers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin +from DashAI.back.models.efficientnet_b0_image_classifier import ( + EfficientNetB0ImageClassifier, +) +from DashAI.back.models.resnet18_image_classifier import ResNet18ImageClassifier +from DashAI.back.models.resnet50_image_classifier import ResNet50ImageClassifier + +_CASES = [ + ResNet18ImageClassifier, + ResNet50ImageClassifier, + EfficientNetB0ImageClassifier, +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_image_classifier_is_downloadable(model_cls): + assert issubclass(model_cls, TorchvisionDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_image_classifier_metadata_flags_download(model_cls): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_is_downloaded_uses_checkpoints_dir(model_cls, component_root): + assert model_cls.is_downloaded() is False + + ckpt = component_root / model_cls.__name__ / "checkpoints" + ckpt.mkdir(parents=True) + (ckpt / "weights.pth").write_bytes(b"w") + assert model_cls.is_downloaded() is True diff --git a/tests/back/models/test_nllb_transformer.py b/tests/back/models/test_nllb_transformer.py index 772e0b8f3..70f79c32b 100644 --- a/tests/back/models/test_nllb_transformer.py +++ b/tests/back/models/test_nllb_transformer.py @@ -27,6 +27,11 @@ def decode(self, token_ids, skip_special_tokens=True): def convert_tokens_to_ids(self, token): return self.lang_code_to_id.get(token, 0) + def save_pretrained(self, save_directory): + save_path = Path(save_directory) + save_path.mkdir(parents=True, exist_ok=True) + (save_path / "tokenizer.json").write_text("{}", encoding="utf-8") + class DummyNllbTokenizerNoLangMap: def __init__(self): diff --git a/tests/back/models/test_opus_mt_downloadable.py b/tests/back/models/test_opus_mt_downloadable.py new file mode 100644 index 000000000..f674b40d8 --- /dev/null +++ b/tests/back/models/test_opus_mt_downloadable.py @@ -0,0 +1,152 @@ +"""Tests that OpusMtTransformerMixin subclasses expose download metadata.""" + +from unittest.mock import MagicMock, patch + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( + OpusMtEnESTransformer, +) + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +def test_opus_mt_is_downloadable(): + assert issubclass(OpusMtEnESTransformer, HFDownloadableMixin) + assert OpusMtEnESTransformer.REQUIRES_DOWNLOAD is True + assert OpusMtEnESTransformer.DOWNLOAD_SIZE_BYTES is not None + + +def test_opus_mt_hf_repos_derived_from_model_name(): + assert OpusMtEnESTransformer.hf_repos() == [("Helsinki-NLP/opus-mt-en-es", "model")] + + +def test_opus_mt_metadata_flags_download(): + meta = OpusMtEnESTransformer.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == OpusMtEnESTransformer.DOWNLOAD_SIZE_BYTES + + +def test_opus_mt_is_downloaded_uses_component_dir(component_root): + assert OpusMtEnESTransformer.is_downloaded() is False + + repo_dir = component_root / "OpusMtEnESTransformer" / "opus-mt-en-es" + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert OpusMtEnESTransformer.is_downloaded() is True + + +# --------------------------------------------------------------------------- +# Self-contained run tests (no real weights required) +# --------------------------------------------------------------------------- + + +@pytest.fixture +def component_root_for_save_load(tmp_path): + """Inject a temporary COMPONENT_PATH so _repo_dir resolves without touching + the real filesystem or requiring a real download. + """ + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +def test_save_persists_tokenizer(tmp_path, component_root_for_save_load): + """save() must call both model.save_pretrained and tokenizer.save_pretrained.""" + mock_tokenizer = MagicMock() + mock_model = MagicMock() + + with ( + patch("transformers.AutoTokenizer") as tok_cls, + patch("transformers.AutoModelForSeq2SeqLM"), + patch("transformers.AutoConfig") as cfg_cls, + ): + tok_cls.from_pretrained.return_value = mock_tokenizer + + instance = OpusMtEnESTransformer( + model=mock_model, + pretrained_dir=str(tmp_path), + num_train_epochs=1, + batch_size=2, + learning_rate=2e-5, + device="CPU", + weight_decay=0.01, + log_train_every_n_epochs=None, + log_train_every_n_steps=None, + log_validation_every_n_epochs=None, + log_validation_every_n_steps=None, + ) + instance.fitted = True + + save_dir = tmp_path / "run" + cfg_cls.from_pretrained.return_value = MagicMock() + + instance.save(save_dir) + + mock_model.save_pretrained.assert_called_once_with(save_dir) + mock_tokenizer.save_pretrained.assert_called_once_with(save_dir) + + +def test_load_tokenizer_from_run_dir_not_download_folder( + tmp_path, component_root_for_save_load +): + """load() must load the tokenizer from the run dir, not the component download + folder. This verifies that trained runs are self-contained. + """ + run_dir = tmp_path / "my_run" + run_dir.mkdir() + + mock_model = MagicMock() + mock_tokenizer = MagicMock() + mock_config = MagicMock() + mock_config.custom_params = { + "num_train_epochs": 1, + "batch_size": 2, + "learning_rate": 2e-5, + "device": "CPU", + "weight_decay": 0.01, + "fitted": True, + } + + with ( + patch("transformers.AutoTokenizer") as tok_cls, + patch("transformers.AutoModelForSeq2SeqLM") as model_cls, + patch("transformers.AutoConfig") as cfg_cls, + ): + tok_cls.from_pretrained.return_value = mock_tokenizer + model_cls.from_pretrained.return_value = mock_model + cfg_cls.from_pretrained.return_value = mock_config + + loaded = OpusMtEnESTransformer.load(run_dir) + + # The tokenizer must be loaded from the run dir. + tok_cls.from_pretrained.assert_called_once_with(str(run_dir)) + + # The component download folder (under COMPONENT_PATH) must NOT be used. + component_download_dir = str( + component_root_for_save_load / "OpusMtEnESTransformer" / "opus-mt-en-es" + ) + for call in tok_cls.from_pretrained.call_args_list: + assert call.args[0] != component_download_dir, ( + "tokenizer was loaded from the component download folder, not the run dir" + ) + + assert loaded.fitted is True diff --git a/tests/back/models/test_opus_mt_en_es_transformer.py b/tests/back/models/test_opus_mt_en_es_transformer.py index 9d3108c6b..a37ec6bba 100644 --- a/tests/back/models/test_opus_mt_en_es_transformer.py +++ b/tests/back/models/test_opus_mt_en_es_transformer.py @@ -12,6 +12,9 @@ def __call__(self, text, truncation=True, padding="max_length", max_length=512): del text, truncation, padding, max_length return {"input_ids": [1, 2, 3], "attention_mask": [1, 1, 1]} + def save_pretrained(self, save_directory): + Path(save_directory).mkdir(parents=True, exist_ok=True) + class DummySeq2SeqModel: def __init__(self): diff --git a/tests/back/models/test_opus_mt_es_en_transformer.py b/tests/back/models/test_opus_mt_es_en_transformer.py index 24f6a2de3..8850ebcd9 100644 --- a/tests/back/models/test_opus_mt_es_en_transformer.py +++ b/tests/back/models/test_opus_mt_es_en_transformer.py @@ -22,6 +22,9 @@ def decode(self, token_ids, skip_special_tokens=True): del token_ids, skip_special_tokens return "translated text" + def save_pretrained(self, save_directory): + Path(save_directory).mkdir(parents=True, exist_ok=True) + class DummySeq2SeqModel: def __init__(self): diff --git a/tests/back/models/test_pixart_tongyi_downloadable.py b/tests/back/models/test_pixart_tongyi_downloadable.py new file mode 100644 index 000000000..982b9d7a7 --- /dev/null +++ b/tests/back/models/test_pixart_tongyi_downloadable.py @@ -0,0 +1,43 @@ +"""Tests that PixArt-Sigma / Tongyi Z-Image models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigma +from DashAI.back.models.hugging_face.tongyi_z_image_model import ( + TongyiZImage, + TongyiZImageTurbo, +) + +# Single-repo checkpoints: hf_repos() is exactly one (repo_id, "model"). +_SINGLE_REPO_CASES = [ + (TongyiZImage, "Tongyi-MAI/Z-Image"), + (TongyiZImageTurbo, "Tongyi-MAI/Z-Image-Turbo"), +] + +_ALL = [PixArtSigma, TongyiZImage, TongyiZImageTurbo] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _SINGLE_REPO_CASES) +def test_single_repo_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +def test_pixart_sigma_downloads_both_checkpoints(): + """PixArt-Sigma downloads the 1024 pipeline and the 512 transformer.""" + assert issubclass(PixArtSigma, HFPretrainedDownloadMixin) + assert PixArtSigma.REQUIRES_DOWNLOAD is True + assert PixArtSigma.DOWNLOAD_SIZE_BYTES is not None + repos = [repo_id for repo_id, *_ in PixArtSigma.hf_repos()] + assert "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" in repos + assert "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" in repos + + +@pytest.mark.parametrize("model_cls", _ALL) +def test_metadata_flags_download(model_cls): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES diff --git a/tests/back/models/test_sd3_downloadable.py b/tests/back/models/test_sd3_downloadable.py new file mode 100644 index 000000000..4e3a3bbd3 --- /dev/null +++ b/tests/back/models/test_sd3_downloadable.py @@ -0,0 +1,33 @@ +"""Tests that Stable Diffusion 3 per-checkpoint models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.stable_diffusion_v3_model import ( + StableDiffusion3Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, + StableDiffusion35Medium, +) + +_CASES = [ + (StableDiffusion3Medium, "stabilityai/stable-diffusion-3-medium-diffusers"), + (StableDiffusion35Medium, "stabilityai/stable-diffusion-3.5-medium"), + (StableDiffusion35Large, "stabilityai/stable-diffusion-3.5-large"), + (StableDiffusion35LargeTurbo, "stabilityai/stable-diffusion-3.5-large-turbo"), +] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd3_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd3_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES diff --git a/tests/back/models/test_sdxl_downloadable.py b/tests/back/models/test_sdxl_downloadable.py new file mode 100644 index 000000000..3239fd924 --- /dev/null +++ b/tests/back/models/test_sdxl_downloadable.py @@ -0,0 +1,31 @@ +"""Tests that SDXL / SDXL-Turbo models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.sdxl_turbo_model import SDXLTurboModel +from DashAI.back.models.hugging_face.stable_diffusion_xl_model import ( + RealVisXLV4, + StableDiffusionXL, +) + +_CASES = [ + (StableDiffusionXL, "stabilityai/stable-diffusion-xl-base-1.0"), + (RealVisXLV4, "SG161222/RealVisXL_V4.0"), + (SDXLTurboModel, "stabilityai/sdxl-turbo"), +] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sdxl_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sdxl_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES diff --git a/tests/back/models/test_stable_diffusion2_downloadable.py b/tests/back/models/test_stable_diffusion2_downloadable.py new file mode 100644 index 000000000..b86b179c6 --- /dev/null +++ b/tests/back/models/test_stable_diffusion2_downloadable.py @@ -0,0 +1,58 @@ +"""Tests that Stable Diffusion 2 per-checkpoint models expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.stable_diffusion_v2_model import ( + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, +) + +_CASES = [ + (StableDiffusion2, "sd2-community/stable-diffusion-2"), + (StableDiffusion2_512, "sd2-community/stable-diffusion-2-base"), + (StableDiffusion21, "sd2-community/stable-diffusion-2-1"), + (StableDiffusion21_512, "sd2-community/stable-diffusion-2-1-base"), +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_is_downloaded_uses_component_dir(model_cls, repo_id, component_root): + assert model_cls.is_downloaded() is False + + leaf = repo_id.split("/")[-1] + repo_dir = component_root / model_cls.__name__ / leaf + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True diff --git a/tests/back/models/test_text_classification_downloadable.py b/tests/back/models/test_text_classification_downloadable.py new file mode 100644 index 000000000..243a05762 --- /dev/null +++ b/tests/back/models/test_text_classification_downloadable.py @@ -0,0 +1,45 @@ +"""Tests that text classification transformers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.hugging_face.distilbert_transformer import DistilBertTransformer + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +def test_text_classification_is_downloadable(): + assert issubclass(DistilBertTransformer, HFDownloadableMixin) + assert DistilBertTransformer.REQUIRES_DOWNLOAD is True + assert DistilBertTransformer.DOWNLOAD_SIZE_BYTES is not None + + +def test_hf_repos_derived_from_model_name(): + assert DistilBertTransformer.hf_repos() == [("distilbert-base-uncased", "model")] + + +def test_metadata_flags_download(): + meta = DistilBertTransformer.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == DistilBertTransformer.DOWNLOAD_SIZE_BYTES + + +def test_is_downloaded_uses_component_dir(component_root): + assert DistilBertTransformer.is_downloaded() is False + + repo_dir = component_root / "DistilBertTransformer" / "distilbert-base-uncased" + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert DistilBertTransformer.is_downloaded() is True diff --git a/tests/back/models/test_translation_downloadable.py b/tests/back/models/test_translation_downloadable.py new file mode 100644 index 000000000..cba69d495 --- /dev/null +++ b/tests/back/models/test_translation_downloadable.py @@ -0,0 +1,58 @@ +"""Tests that translation transformers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) +from DashAI.back.models.hugging_face.m2m100_transformer import M2M100Transformer +from DashAI.back.models.hugging_face.nllb_transformer import NllbTransformer +from DashAI.back.models.hugging_face.t5_small_transformer import T5SmallTransformer + +_CASES = [ + (M2M100Transformer, "facebook/m2m100_418M"), + (NllbTransformer, "facebook/nllb-200-distilled-600M"), + (T5SmallTransformer, "t5-small"), +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_is_downloaded_uses_component_dir( + model_cls, repo_id, component_root +): + assert model_cls.is_downloaded() is False + + leaf = repo_id.split("/")[-1] + repo_dir = component_root / model_cls.__name__ / leaf + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True diff --git a/tests/back/registries/test_download_status.py b/tests/back/registries/test_download_status.py new file mode 100644 index 000000000..ff86ac8bb --- /dev/null +++ b/tests/back/registries/test_download_status.py @@ -0,0 +1,37 @@ +from DashAI.back.config_object import ConfigObject +from DashAI.back.dependencies.downloads.downloadable import DownloadableMixin +from DashAI.back.dependencies.registry import ComponentRegistry + + +class _FakeBase(ConfigObject): + TYPE = "Model" + + +_STATE = {"downloaded": False} + + +class _FakeDownloadable(DownloadableMixin, _FakeBase): + DOWNLOAD_SIZE_BYTES = 10 + + @classmethod + def is_downloaded(cls): + return _STATE["downloaded"] + + @classmethod + def download(cls, report=None): ... + @classmethod + def delete(cls): ... + + +def test_seed_sets_downloaded_flag(): + _STATE["downloaded"] = False + registry = ComponentRegistry(initial_components=[_FakeDownloadable]) + assert registry["_FakeDownloadable"]["downloaded"] is False + + +def test_refresh_reconciles_single_component(): + _STATE["downloaded"] = False + registry = ComponentRegistry(initial_components=[_FakeDownloadable]) + _STATE["downloaded"] = True + assert registry.refresh_download_status("_FakeDownloadable") is True + assert registry["_FakeDownloadable"]["downloaded"] is True diff --git a/tests/back/registries/test_registry.py b/tests/back/registries/test_registry.py index c15b17238..43bcda0de 100644 --- a/tests/back/registries/test_registry.py +++ b/tests/back/registries/test_registry.py @@ -70,6 +70,19 @@ class ComponentWithTwoBaseClasses(BaseConfigComponent1, BaseConfigComponent2): . class NoComponent: ... +class RelatedMixin: + COMPATIBLE_COMPONENTS = ["Component1"] + + +class RelatedParentComponent(BaseStaticComponent): + COMPATIBLE_COMPONENTS = ["Component2"] + + +class CombinedRelatedComponent(RelatedMixin, RelatedParentComponent): + # Redeclares an inherited entry on purpose: the union must deduplicate. + COMPATIBLE_COMPONENTS = ["Component1"] + + COMPONENT1_DICT = { "name": "Component1", "type": "ConfigComponent1", @@ -80,6 +93,7 @@ class NoComponent: ... "description": None, "display_name": None, "color": None, + "downloaded": True, } COMPONENT2_DICT = { "name": "Component2", @@ -91,6 +105,7 @@ class NoComponent: ... "description": None, "display_name": None, "color": None, + "downloaded": True, } SUBCOMPONENT1_DICT = { "name": "SubComponent1", @@ -102,6 +117,7 @@ class NoComponent: ... "description": None, "display_name": None, "color": None, + "downloaded": True, } COMPONENT3_DICT = { "name": "Component3", @@ -113,6 +129,7 @@ class NoComponent: ... "description": "Some static component", "display_name": None, "color": None, + "downloaded": True, } COMPONENT3_DICT_MS = COMPONENT3_DICT.copy() COMPONENT3_DICT_MS["description"] = MultilingualString(en="Some static component") @@ -127,6 +144,7 @@ class NoComponent: ... "description": None, "display_name": None, "color": None, + "downloaded": True, } RELATED_COMPONENT2_DICT = { "name": "RelatedComponent2", @@ -138,6 +156,7 @@ class NoComponent: ... "description": None, "display_name": None, "color": None, + "downloaded": True, } @@ -482,3 +501,51 @@ def test_relationships_module(): COMPONENT1_DICT, COMPONENT2_DICT, ] + + +def test_get_related_components_skips_unregistered_names(): + test_registry = ComponentRegistry(initial_components=[Component1]) + + # RelatedComponent2 declares Component1 (registered) and Component2 + # (NOT registered): lookups must skip the unregistered name. + test_registry.register_component(RelatedComponent2) + + assert test_registry.get_related_components("RelatedComponent2") == [ + COMPONENT1_DICT + ] + assert [ + component["name"] + for component in test_registry.get_related_components("Component1") + ] == ["RelatedComponent2"] + + +def test_compatible_components_merge_across_bases(): + test_registry = ComponentRegistry( + initial_components=[ + Component1, + Component2, + ] + ) + + test_registry.register_component(CombinedRelatedComponent) + + # The mixin contributes "Component1", the static base "Component2"; + # the subclass redeclaration of "Component1" does not duplicate it. + assert test_registry._relationship_manager["CombinedRelatedComponent"] == [ + "Component1", + "Component2", + ] + assert test_registry.get_related_components("CombinedRelatedComponent") == [ + COMPONENT1_DICT, + COMPONENT2_DICT, + ] + assert [ + component["name"] + for component in test_registry.get_related_components("Component2") + ] == ["CombinedRelatedComponent"] + + # Unregistering removes both inherited edges symmetrically. + test_registry.unregister_component(CombinedRelatedComponent) + assert test_registry._relationship_manager["CombinedRelatedComponent"] == [] + assert test_registry.get_related_components("Component1") == [] + assert test_registry.get_related_components("Component2") == [] diff --git a/update-server.sh b/update-server.sh index b1449f2b5..2b771ea5d 100644 --- a/update-server.sh +++ b/update-server.sh @@ -89,7 +89,13 @@ success "Frontend built." # ── Python dependencies ─────────────────────────────────────────────────────── info "Installing Python dependencies..." -pip install -r requirements.txt +if command -v uv &>/dev/null; then + # uv targets the active conda env and reads deps from pyproject.toml + uv pip install -e . +else + warn "uv not found, falling back to pip (consider installing uv: https://docs.astral.sh/uv/)." + pip install -e . +fi success "Python dependencies installed." # ── Restart service ─────────────────────────────────────────────────────────── diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..ee6e72cb2 --- /dev/null +++ b/uv.lock @@ -0,0 +1,9474 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +conflicts = [[ + { package = "dashai", extra = "cpu" }, + { package = "dashai", extra = "cuda" }, +]] + +[[package]] +name = "accelerate" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" }, +] + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/4d/4a99fb425c5e0cad715eea7bd190aff46f38b959a0a2dadb993705d34b26/aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", size = 765848, upload-time = "2026-07-23T01:52:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/43b85dc55b8e950dc644babe762add781319ea881b57b33d2cce12017d12/aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", size = 517476, upload-time = "2026-07-23T01:52:10.846Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9e/73b582c4dbbc3c12ef4473822475effaabf1f934b56f14f5b03fe5d3a2af/aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5", size = 515334, upload-time = "2026-07-23T01:52:12.636Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/e98c3c9e05a5bdf97defe5ff9169baba4f0ec9a901f2d60e0f060c2f051e/aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", size = 1708830, upload-time = "2026-07-23T01:52:14.538Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2c/26e60b694844dfd2176c57f913a22d0cd6a16f9ff202cbda7580d0328b98/aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", size = 1674012, upload-time = "2026-07-23T01:52:16.486Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/672df92e3172cd876aacfa97a952ac560877eb169384b2991ac5b273de4c/aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", size = 1767015, upload-time = "2026-07-23T01:52:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/228dec7bfec1c373cc2217cdeb47d6456dcd7a13a4c55144930a75ae3851/aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", size = 1858700, upload-time = "2026-07-23T01:52:20.08Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ff/cb36724e8c8d17f90ada567a9ff3efe1d6e9b549fba697a242aece180f21/aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", size = 1714075, upload-time = "2026-07-23T01:52:22.071Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3a/296a4135c6366376263aeef54b15caca1f07676c2ae0c525d7832f2f808a/aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", size = 1588234, upload-time = "2026-07-23T01:52:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/7d/81/9d5d853ef892dc066d1eb6db0e87a47348b920c1c879aa554612fdbd9d79/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", size = 1677300, upload-time = "2026-07-23T01:52:25.861Z" }, + { url = "https://files.pythonhosted.org/packages/68/96/021d386ae32d9b26d4b88df2e794546232ff56bb6be952bf6be227c0bbc7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", size = 1691501, upload-time = "2026-07-23T01:52:28Z" }, + { url = "https://files.pythonhosted.org/packages/29/9f/af66adce26a14af135c003cbd0f44ccaa68cebd30ff8ac99ca47fb4958f7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", size = 1735113, upload-time = "2026-07-23T01:52:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/28c390d4c9851effe52ac25b5a2e1d92246acd00728b4fc7975dafb67484/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", size = 1577486, upload-time = "2026-07-23T01:52:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/db/c2/00e23a1bf2abb70dd353f6987db7e7f2491d0261f7363997738c71c98f95/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", size = 1751353, upload-time = "2026-07-23T01:52:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7d/d51a706a8cbfa57f0611127daf61ab3ae02ab8420b0407412079227d1c65/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", size = 1698681, upload-time = "2026-07-23T01:52:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/90bd5cd9fdd9787cb4211d284d1fb8401339a933cb0227a15b71e789232f/aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", size = 456733, upload-time = "2026-07-23T01:52:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/fe5b8f6a71ae112bc677163d0b0701bda5dc15005249582258ede0eb88c7/aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", size = 480460, upload-time = "2026-07-23T01:52:43.905Z" }, + { url = "https://files.pythonhosted.org/packages/54/00/45e98b6645cd7f00a4b78b749ebd309094b0eaeb2d2e96157eadbc0d0050/aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", size = 453479, upload-time = "2026-07-23T01:52:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "alembic" +version = "1.18.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" }, +] + +[[package]] +name = "arviz" +version = "0.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5netcdf" }, + { name = "h5py" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "platformdirs" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools" }, + { name = "typing-extensions" }, + { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray-einstats", version = "0.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray-einstats", version = "0.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray-einstats", version = "0.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/c9/9c853633715f972eecc20995763c6e3005a3afcdcf47e39d20cd1c2889cd/arviz-0.23.4.tar.gz", hash = "sha256:611be826995066036c9443ea98d11486c279ef3da3b6cdc5c0816fab434115b9", size = 1592968, upload-time = "2026-02-04T17:57:53.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/1f/227f9cb7edcd3e14ab05928f3db00e9d595c0f269c87bf35f565ce44941b/arviz-0.23.4-py3-none-any.whl", hash = "sha256:c46c7faf8a06abadc9b5b64000584062ecbc20c2298e2bd6dfba04bb01a684ca", size = 1673773, upload-time = "2026-02-04T17:57:51.778Z" }, +] + +[[package]] +name = "arviz-base" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/b7/6567bb2fff93232635f0ba1c6f8dceee9b1271e94165a4d9dd6c40ff5636/arviz_base-0.8.2.tar.gz", hash = "sha256:4d77142c6396c648fef3624b2d3f76fc9f326b88e513b34eff15ac06489cf840", size = 1403232, upload-time = "2026-01-16T14:00:48.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/31/0491ae7431f50708a0d60d54adf363b8e874d09af9d867e813fb4751c590/arviz_base-0.8.2-py3-none-any.whl", hash = "sha256:84f8c72d1454e21198f9188537d10fedba8db23ee4abc54200c38c808b76e05c", size = 1420673, upload-time = "2026-01-16T14:00:46.408Z" }, +] + +[[package]] +name = "arviz-base" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "lazy-loader", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/d7/4db6e89b0cc5a26ce0e84accd5dd6ff43572eb32e9b46c577bd5d219ed6a/arviz_base-1.2.0.tar.gz", hash = "sha256:be06f9c15c53a951a971bef697e6b0a68497aae4d1670be065dd8f0482e9efca", size = 1410416, upload-time = "2026-06-12T15:54:46.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/d7/82e5f456d50b6accf06d137594282bf6984526fa1b329a061f7b91b123aa/arviz_base-1.2.0-py3-none-any.whl", hash = "sha256:a3f7023b665823068ff4b973bb6205eacc65f3c0a446cc099d7e5326b50f1ffc", size = 1427453, upload-time = "2026-06-12T15:54:44.805Z" }, +] + +[[package]] +name = "arviz-stats" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/c3/34c8e1baa44b100c137fe8df0421f179241846c2e4311775ba29d2af38bd/arviz_stats-0.8.0.tar.gz", hash = "sha256:633576cbb488aeb50608466fe2c0c79926d599ec1937d24636ba88c0cdfa224f", size = 137513, upload-time = "2026-01-15T16:08:57.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/08/8a8ad310c116a5574468c0fa2f37a418769719d9ff5127dc817c54ce20b2/arviz_stats-0.8.0-py3-none-any.whl", hash = "sha256:c80553f11c590219c6eabdee6d67fb100c31f8751790772bdc269a9970429913", size = 164648, upload-time = "2026-01-15T16:08:55.653Z" }, +] + +[package.optional-dependencies] +xarray = [ + { name = "arviz-base", version = "0.8.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray-einstats", version = "0.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "arviz-stats" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/97/fd555a4b16ac349f297c786dab1a3270b3540677b0222a84e517441eb338/arviz_stats-1.2.0.tar.gz", hash = "sha256:fc49e6e75f4fce953987a9bf17dc39950e1f12e7cd73f865257e5d1b6a5ee114", size = 157554, upload-time = "2026-06-12T16:20:11.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8f/73f43d90534d49a4af4c8e35d5b60e2838435b0318f44262dc6fe2dd39d8/arviz_stats-1.2.0-py3-none-any.whl", hash = "sha256:f9084addeb1abdbab6e9816f0a063118bc4ff7c48ce9141a4dad20b0e41410ae", size = 183844, upload-time = "2026-06-12T16:20:10.191Z" }, +] + +[package.optional-dependencies] +xarray = [ + { name = "arviz-base", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray-einstats", version = "0.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "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 = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "bottle" +version = "0.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/71/cca6167c06d00c81375fd668719df245864076d284f7cb46a694cbeb5454/bottle-0.13.4.tar.gz", hash = "sha256:787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47", size = 98717, upload-time = "2025-06-15T10:08:59.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/f6/b55ec74cfe68c6584163faa311503c20b0da4c09883a41e8e00d6726c954/bottle-0.13.4-py2.py3-none-any.whl", hash = "sha256:045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e", size = 103807, upload-time = "2025-06-15T10:08:57.691Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" }, +] + +[[package]] +name = "certifi" +version = "2026.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 = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "clr-loader" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu') or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/46/7eea92b6aa2d68af78e049cbecec5f757f1aad44ecdecdc16bbad7eead51/clr_loader-0.3.1.tar.gz", hash = "sha256:2e073e9aaf49d1ae2f56ecba27987ad5fb68be4bcd9dd34a5bed8f0e4e128366", size = 86805, upload-time = "2026-04-18T17:49:44.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/da/ec1a6e36624000b6df0dd61183c42342ee5814c073315e802cadaad04d2f/clr_loader-0.3.1-py3-none-any.whl", hash = "sha256:cbad189de20d202a7d621956b0fc38049e13c9bf7ca2923441eff725cd121aa1", size = 55730, upload-time = "2026-04-18T17:49:42.99Z" }, +] + +[[package]] +name = "cmaes" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/9f/ae4edb7dec820e84fef7a90b753ae5c72c66a05ffa69a7894771024386a7/cmaes-0.13.0.tar.gz", hash = "sha256:69a252b0291d08100351e37c2918c7c6d929b02ab7dcd9dd14fc02c7c98cc1b9", size = 61265, upload-time = "2026-03-28T07:41:55.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/98/be3f668f77838b2756ccc78a45e0c62f43d3134003f3f4bad814d37df1b3/cmaes-0.13.0-py3-none-any.whl", hash = "sha256:ccf61c73d5792cf44b50672b63f28c590082d1c1f5ab5155e2dd6e5305427cad", size = 73027, upload-time = "2026-03-28T07:41:53.956Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "colorlog" +version = "6.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151, upload-time = "2026-07-23T13:40:40.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239, upload-time = "2026-07-23T13:40:39.562Z" }, +] + +[[package]] +name = "cons" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "logical-unification" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/20/0eca1dcdbac64a570e60df66119847f94cdd513178d9c222c15101ca1022/cons-0.4.7.tar.gz", hash = "sha256:0a96cd2abd6a9f494816c1272cf5583a960041750c2d7a48eeeccd47ce369dfd", size = 8690, upload-time = "2025-07-11T18:01:31.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/9f/bffa3362895e5437d9d12e3bbd242f86d91af1d7cd26f6e14ebb6376581b/cons-0.4.7-py3-none-any.whl", hash = "sha256:e38ee12cf703559ea744c94f725bee0e2329f32daf0249b49db1b0437cc6cb94", size = 8603, upload-time = "2025-07-11T18:01:28.706Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "controlnet-aux" +version = "0.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "opencv-python-headless" }, + { name = "pillow" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "timm" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/ad/2eb8cd9a8e17e35b9e5d39ad29afdde8fe810bda85e6e59117050519955d/controlnet_aux-0.0.10.tar.gz", hash = "sha256:31dc265a54448bdcee033a130b47423c80587fa35ccac752113af1b4d48f5183", size = 215016, upload-time = "2025-05-08T10:38:30.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/3b/e1608b5bca98bcdaa2c74286fcc0457f3c0a8c99e25e3e4065c184f92c7f/controlnet_aux-0.0.10-py3-none-any.whl", hash = "sha256:cad3480d62c7df1ae569258a659c88d730873323d2adbb9f6f7aebd87917df3b", size = 290398, upload-time = "2025-05-08T10:38:28.952Z" }, +] + +[[package]] +name = "coverage" +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/060ce69008ac97bbc01b1411b3e55b61f6f015659400b46749b662107831/coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d", size = 221284, upload-time = "2026-07-15T18:53:29.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a3/d936e8b53edd9684100a6aefaf3fcabaa54728fe33324436c8d279c047aa/coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846", size = 221799, upload-time = "2026-07-15T18:53:31.708Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a3/ca234b06aec7ee28226f11d39a696b4481fe5eddfce8e03bf39979bb8ffb/coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf", size = 248544, upload-time = "2026-07-15T18:53:33.212Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/dda79527bb7573ba91828b2fb91b3105d87378d6a2749ca0c0924ce0addd/coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376", size = 250374, upload-time = "2026-07-15T18:53:34.683Z" }, + { url = "https://files.pythonhosted.org/packages/67/c6/c33755a34572f81f49a8c0cdf6b622f35ccb3238b136e1909daf0cdd4319/coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd", size = 252239, upload-time = "2026-07-15T18:53:36.205Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/dc341741b375be53a5baeee5b4bf0f0e525d38caed428f7932d23bb7bcb1/coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb", size = 254150, upload-time = "2026-07-15T18:53:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8d/966a18a5b195cb4e77b14c53f5f3dce22b5da05e6de7fafd1e08f2d2067a/coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1", size = 249234, upload-time = "2026-07-15T18:53:39.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/8b2e367496ab48484d48e79984fec76cdc1b7cb5d3a00ee799a5602e3ec9/coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9", size = 250276, upload-time = "2026-07-15T18:53:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/63/92/1199318a200eb6c8c6ce0192c892c8710ac791abbe0f35099294620bbfda/coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a", size = 248283, upload-time = "2026-07-15T18:53:42.557Z" }, + { url = "https://files.pythonhosted.org/packages/56/da/be284a55c5619bda891a89c27dfd59324a2c6a14d755cf6aac6960ceebeb/coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287", size = 252093, upload-time = "2026-07-15T18:53:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/d4/53/ee112da833ddd77b73c6d781a98029b45b584b136615b4900ed0569f887e/coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89", size = 248552, upload-time = "2026-07-15T18:53:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/82/6a/802cfc802e9113494c80bf3f284cd4d72faeb1f24e244f61046af364f2ca/coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88", size = 249154, upload-time = "2026-07-15T18:53:47.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/529808e91d651147edae408fd9e894abc3b8cad7f3e594bbc36719a3e13a/coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443", size = 223334, upload-time = "2026-07-15T18:53:48.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/0f/0e1829d7001130876dfbc0b4e1c737ea7c155b809e3e4a98a0aa268e2369/coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629", size = 223959, upload-time = "2026-07-15T18:53:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cuda-bindings" +version = "12.9.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-pathfinder", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/2e/b1b14be5884519917f9df4d9106d3e16575c19fa847d13a3f6e9d272b5cd/cuda_bindings-12.9.7-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a318075ef3277ca2fdd7df5d4bf671388696b4f2b65e2c4483f1853517692e3b", size = 7127291, upload-time = "2026-05-27T18:44:02.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1f/0809c53f694693d703c9efee0379875089db17ab50196845e08f6c686fd4/cuda_bindings-12.9.7-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f95debd2c54c5f087462668511bcb2b1295baa389cd6de9e768212f3cab2bbe2", size = 7657751, upload-time = "2026-05-27T18:44:04.365Z" }, + { url = "https://files.pythonhosted.org/packages/19/26/fdd044c00e8f20f783bcb8b9cce0144fbefacf6ed23a64318dc1c4d8db99/cuda_bindings-12.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:62e245bfa4830d473b038d0d7e5ab9cc1b377a09d2bc9afceaf5c5bab96ab1c6", size = 7190277, upload-time = "2026-05-27T18:44:06.044Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/f9d1095f90d2a4df24cfcafe7487fd9444c6dacb94e3722be6fedd8ac26c/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16043ef5b15ab88fe9954c5c2061b1d8007591b27f2c916331056de0ebc6187e", size = 7114834, upload-time = "2026-05-27T18:44:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8a/1251e1794b69865aacd5629936006b18ea0816a495de4ecea9a825556eb3/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6496a88d84b1209d6651b0370c19c26319e157c22f6d018bf9a358cd8049041", size = 7647147, upload-time = "2026-05-27T18:44:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/158392f6572e6e0def70ca39029c46b75e02ea4a43c63ff7320b3d180a29/cuda_bindings-12.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:c392ffa5010ef4073bfd9dfff4d1ae56032094ed52d3d732014f8e41a73e6b59", size = 7218081, upload-time = "2026-05-27T18:44:11.104Z" }, + { url = "https://files.pythonhosted.org/packages/32/45/557d4ed1fa54f0c7db8aee083229f624990d69f7d00f55477eed5c7e169a/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0666d3c082ef8f4b2d670950589373550e9f3bf564d635dd883f24a0b40402ff", size = 7071026, upload-time = "2026-05-27T18:44:13.356Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/e3c6e58ece26a053419ba0a18444b5443cfc64451bbf37f84e8143b8bdca/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c7ef48c5e13ae90f3b2ecfb72f8e99ac43c8f4c43e67e1325b8aae331453687", size = 7611059, upload-time = "2026-05-27T18:44:15.252Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/afaa3de4d491a55af8961081e0b69c08d51bfbe471c359a7bddb4a28ca41/cuda_bindings-12.9.7-cp312-cp312-win_amd64.whl", hash = "sha256:3c089aaf4f5f570ec50244c68f5a2b00a2c9a8e01e04219fd2e36e340be0d88b", size = 7400841, upload-time = "2026-05-27T18:44:17.164Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7b/f1575e41e1a17dc2f2a408b2e8e864c9324e41e3e23f6401e5efc54c152a/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:266379e4942051f544a8e7ea1a30ead8d7e8199b6b30fcdc8917cae2bf614e61", size = 6978549, upload-time = "2026-05-27T18:44:18.839Z" }, + { url = "https://files.pythonhosted.org/packages/9d/dc/62d62eb4f91eb721bcf46da51b13e9872ccd8fa7e60eb8ba7b7baeac72c6/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59cf4a37b0d662ba15037c9ceebe1a306ebf2c01a8235a09be13cd07094fdb74", size = 7457675, upload-time = "2026-05-27T18:44:20.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/b2/753fe88151001d0dc23f56a8e119fe06b991b0d1a885fa02f9852b12f523/cuda_bindings-12.9.7-cp313-cp313-win_amd64.whl", hash = "sha256:5bd89dcb78475a6d8a4620ea94b74edf0cbbeacee6d1622d8f94452c1e8d3f15", size = 7360097, upload-time = "2026-05-27T18:44:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f9/77/94d9b85f26add6fe9c9cb7c4ec3b96bc598f7ea5cfbd7490cc0a36adf5be/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dbcd4801954eb3508f4dc2fa0d0c8eb93eb3f45326fd61be2731418c371e7a0", size = 6870886, upload-time = "2026-05-27T18:44:24.164Z" }, + { url = "https://files.pythonhosted.org/packages/04/dd/3ec34b569e1b990b11276feba306bf8f446656cc38e8ed0f49b5facfeffa/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3747ea132642416786a8e31bf229032df3a7856911ae5426a7be53d032df183d", size = 7345663, upload-time = "2026-05-27T18:44:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c8/d79a20ba396e7ab2dfdd4b72b62356972b25b88aee2ded49a70c797ddea1/cuda_bindings-12.9.7-cp313-cp313t-win_amd64.whl", hash = "sha256:64f7ade7a7a3b69001489753acc21706d9dbda32db8deb68a767a0a0aab30b68", size = 7780136, upload-time = "2026-05-27T18:44:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/68/e4/075052d42872cf8162da53f14447a4b8abc004c3750e4b724ee502428da0/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775960ac9e530717f3b48e165cc6f68684fa9a4141764fd923e4c1a9820acc73", size = 7060090, upload-time = "2026-05-27T18:44:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/ec/cd/3289c810a4d45e5364a3387a74b4c9b6f6f57ee96ae0e5b537cc61dec242/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c47ec1a7a441d91aab32339951df7a1be53451121a12c094bba51467717a35a", size = 7504419, upload-time = "2026-05-27T18:44:31.992Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a0/c429fdcfa5aae181415504c5085ea5944f782b417dd16a7f2a14be0da80d/cuda_bindings-12.9.7-cp314-cp314-win_amd64.whl", hash = "sha256:1e2a4f2ec5b67408c04bb4fbed45d214b66de1f00ee2e972865cacb8708d4e1e", size = 7493876, upload-time = "2026-05-27T18:44:33.618Z" }, + { url = "https://files.pythonhosted.org/packages/11/43/472a6281c3d94e71687e27c657a8f60718d3579b4d94c41deea503165f8a/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00a833d399b31071fab4cf3de2929840ae462dc4848116eeff033d09219e7116", size = 6899146, upload-time = "2026-05-27T18:44:35.556Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/10c1d0b32a9da65142d213e0733d748457fb3fd066aee4317335266f15c6/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11aeafa2b33995f890086b3fb0f062075176d956e9b6a6fe1a699dddc413f6ad", size = 7369087, upload-time = "2026-05-27T18:44:37.359Z" }, + { url = "https://files.pythonhosted.org/packages/33/10/c71a07cd2a1d4db119bada1848b4752a874ccfe4927d419bfdd05f250920/cuda_bindings-12.9.7-cp314-cp314t-win_amd64.whl", hash = "sha256:ece8dfbc22e6de96a26940ab9887eb3cfe1fc1bc3966169391cdb866bb82bb64", size = 8208198, upload-time = "2026-05-27T18:44:39.053Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-pathfinder", marker = "(python_full_version < '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/fc/64/bb17e4d168569ef7be05c44474fe3dc19278d60a69ba228e45a431c86444/cuda_bindings-13.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:c0c4b1a995098c46695c24257a342dc97d6e6d3f3050b944c9f43bd26d734051", size = 5625597, upload-time = "2026-05-29T23:11:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/93/f7/0e35987a21914f84068061dcf4b61466ccbce1c62ddc9727596d5ed0c26f/cuda_bindings-13.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:507b0e19e7f934c5e30f30f0244ad70a75812619a7d3a0d742543caae1bd50f1", size = 5664286, upload-time = "2026-05-29T23:11:47.719Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/7c/95/872a0392122f1fb43fcb06869790ef3171f37beee9f7db8f441739113570/cuda_bindings-13.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:b134dd8c5c66ae4c4ad814f7aee88fd215353c077010cbc47e3b55ed35ec9eff", size = 5875099, upload-time = "2026-05-29T23:11:54.635Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2f/6a0dd496550c6fafbf6aeb1bf40242eeabb2fd138a43892aabb4be8224c2/cuda_bindings-13.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:18c8c167c8907b8f02531ca810534315c458dabef31f7965095619bf647b9202", size = 5830027, upload-time = "2026-05-29T23:12:01.205Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/2734be44dbc80ac082ec23a86b41c8294992dcb90033645ed1bc50aafe4c/cuda_bindings-13.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:8de12ef60bf40756852cb62bbb40460609269f6ece522903d1cc93d73a3ececb", size = 5961055, upload-time = "2026-05-29T23:12:07.971Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/27/2a/b59bcac016ab9985d6b48a5d05b0d698461a159ca03ee11c4abd54da2ac4/cuda_bindings-13.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61120b5e4f4a63f67efd7e7396914cb9ef871bb1f0021e990fb70277be240a4d", size = 6740329, upload-time = "2026-05-29T23:12:15.153Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b4/d088047afe39827556df21118cac9ffd20cc3f968c99a7681494d1eb333c/cuda_pathfinder-1.6.0-py3-none-any.whl", hash = "sha256:1503af579d8379c24bdd65528379bc57039b0455be9f5f9686cf8e473a1fce51", size = 54591, upload-time = "2026-07-21T15:03:56.224Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "12.8.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/c8/7dce3a0b15b42a3b58e7d96eb22a687d3bf2c44e01d149a6874629cd9938/cuda_toolkit-12.8.1-py2.py3-none-any.whl", hash = "sha256:adc7906af4ecbf9a352f9dca5734eceb21daec281ccfcf5675e1d2f724fc2cba", size = 2283, upload-time = "2025-08-13T02:03:07.842Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufft = [ + { name = "nvidia-cufft-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufile = [ + { name = "nvidia-cufile-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +curand = [ + { name = "nvidia-curand-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusolver = [ + { name = "nvidia-cusolver-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusparse = [ + { name = "nvidia-cusparse-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvtx = [ + { name = "nvidia-nvtx-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'aarch64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine == 'AMD64' and sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'linux' and sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "dashai" +version = "0.9.7" +source = { editable = "." } +dependencies = [ + { name = "accelerate" }, + { name = "alembic" }, + { name = "arviz" }, + { name = "beartype" }, + { name = "cmaes" }, + { name = "controlnet-aux" }, + { name = "datasets" }, + { name = "dice-ml" }, + { name = "diffusers" }, + { name = "evaluate" }, + { name = "fastapi", extra = ["all"] }, + { name = "filetype" }, + { name = "grad-cam" }, + { name = "greenery" }, + { name = "httpx" }, + { name = "huey" }, + { name = "hyperopt" }, + { name = "ijson" }, + { name = "imblearn" }, + { name = "joblib" }, + { name = "kink" }, + { name = "lime" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-ml-py" }, + { name = "opencv-python" }, + { name = "openml" }, + { name = "openpyxl" }, + { name = "optuna" }, + { name = "oslo-concurrency", version = "7.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-concurrency", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "plotly" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pymc-bart", version = "0.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pymc-bart", version = "0.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pywebview" }, + { name = "rich" }, + { name = "sacrebleu" }, + { name = "scikit-learn" }, + { name = "sentencepiece" }, + { name = "setuptools" }, + { name = "shap" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "streaming-form-data" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchmetrics" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "transformers" }, + { name = "typer" }, + { name = "wordcloud" }, + { name = "xlrd" }, +] + +[package.optional-dependencies] +cpu = [ + { name = "llama-cpp-python", version = "0.3.34", source = { registry = "https://abetlen.github.io/llama-cpp-python/whl/cpu" } }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +cuda = [ + { name = "llama-cpp-python", version = "0.3.34", source = { registry = "https://pypi.org/simple" } }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx-rtd-theme" }, + { name = "sqlalchemy-stubs" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate" }, + { name = "alembic" }, + { name = "arviz", specifier = "<1.0.0" }, + { name = "beartype" }, + { name = "cmaes" }, + { name = "controlnet-aux" }, + { name = "datasets" }, + { name = "dice-ml", specifier = ">=0.12" }, + { name = "diffusers" }, + { name = "evaluate" }, + { name = "fastapi", extras = ["all"] }, + { name = "filetype" }, + { name = "grad-cam", specifier = ">=1.5.5" }, + { name = "greenery", specifier = "==3.2" }, + { name = "httpx" }, + { name = "huey" }, + { name = "hyperopt" }, + { name = "ijson" }, + { name = "imblearn" }, + { name = "joblib" }, + { name = "kink" }, + { name = "lime", specifier = ">=0.2.0.1" }, + { name = "llama-cpp-python", marker = "extra == 'cpu'", index = "https://abetlen.github.io/llama-cpp-python/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "llama-cpp-python", marker = "extra == 'cuda'" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "numpy" }, + { name = "nvidia-ml-py" }, + { name = "opencv-python" }, + { name = "openml" }, + { name = "openpyxl" }, + { name = "optuna" }, + { name = "oslo-concurrency" }, + { name = "pandas", specifier = "<3.0.0" }, + { name = "pillow" }, + { name = "plotly" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pymc-bart" }, + { name = "pywebview" }, + { name = "rich" }, + { name = "sacrebleu" }, + { name = "scikit-learn", specifier = "<1.8.0" }, + { name = "sentencepiece" }, + { name = "setuptools", specifier = ">=65.0.0,<82" }, + { name = "shap", specifier = ">=0.48" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "streaming-form-data" }, + { name = "torch" }, + { name = "torch", marker = "extra == 'cpu'", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "torch", marker = "extra == 'cuda'", index = "https://download.pytorch.org/whl/cu128", conflict = { package = "dashai", extra = "cuda" } }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "torchvision", marker = "extra == 'cpu'", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "torchvision", marker = "extra == 'cuda'", index = "https://download.pytorch.org/whl/cu128", conflict = { package = "dashai", extra = "cuda" } }, + { name = "transformers" }, + { name = "typer" }, + { name = "wordcloud" }, + { name = "xlrd" }, +] +provides-extras = ["cpu", "cuda"] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, + { name = "sqlalchemy-stubs" }, +] + +[[package]] +name = "datasets" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/5b/836516269d4f618efe621661cfb6f9acc57e6f95265db3efaee48a5ffe04/datasets-5.0.1.tar.gz", hash = "sha256:ce22bb851efd7494f08aad33b940803784434f6e77763d00679a0dc45fcf686a", size = 641498, upload-time = "2026-07-28T11:09:12.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/0b/98fc6eb83333508ca5f44c52b3e287ea8137a0ad582714e2cbc67a02154b/datasets-5.0.1-py3-none-any.whl", hash = "sha256:9fbf73688f8c18f7529b4fe592abd04015f81d1e58001e4bac73ffb2b39d7cc4", size = 559079, upload-time = "2026-07-28T11:09:10.266Z" }, +] + +[[package]] +name = "debtcollector" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/57/1bbe02be744995408d944cf46b8c818cf072873064b1cd3c79c11618b216/debtcollector-3.1.0.tar.gz", hash = "sha256:278a45608cf16e79c0ae10851d869185c6b78f86610df8f27a451a18c1fec732", size = 32951, upload-time = "2026-03-24T10:07:38.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/05/3f36aed56f0e1815fdc2ed4a9f2bd680a7bfe8819f21eacded2dc00fe283/debtcollector-3.1.0-py3-none-any.whl", hash = "sha256:c64e49a66c0b71289620fc2fdf89c03d740bddb20576ddd4f04ddc01da946668", size = 24408, upload-time = "2026-03-24T10:07:37.218Z" }, +] + +[[package]] +name = "detect-installer" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/ce/6897d812825e9d4c53e3c7112726e800cc5231b013b2223bf64f653ff362/detect_installer-0.1.0.tar.gz", hash = "sha256:00ad7ba0a36e3cf7d08a40d3643011746dbc112597c7d475cc91c416710ca4e7", size = 3049, upload-time = "2026-02-23T10:40:22.567Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/34/8cc73273414405086c58852916e4031812a6a30fe04c057e37ad99397b7f/detect_installer-0.1.0-py3-none-any.whl", hash = "sha256:034fb20fd665c36e6ba52b8821525ea07fb4f7f938cac459df889fb33801528a", size = 4539, upload-time = "2026-02-23T10:40:23.807Z" }, +] + +[[package]] +name = "dice-ml" +version = "0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "lightgbm" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas" }, + { name = "raiutils" }, + { name = "scikit-learn" }, + { name = "tqdm" }, + { name = "xgboost", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xgboost", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/84/05049e71e51caf266c89f6eec4c93c90e0c086d3b75c30c7ffa4d4dd40dc/dice_ml-0.12.tar.gz", hash = "sha256:3e40771ef82ad1084ffe1dd098b801f9cd9d7cdf40efba1b85e38a615ae5a75b", size = 15024998, upload-time = "2025-07-13T17:35:33.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/a2/63c11da0358ac2e931b0ab2e2cf203d9a234beeb201d62708733f7f7eea7/dice_ml-0.12-py3-none-any.whl", hash = "sha256:77d8195a40e36ff82ffa4c7fc4d19f364f099497b1be46b2123c7396a2e4bbae", size = 2528224, upload-time = "2025-07-13T17:35:31.115Z" }, +] + +[[package]] +name = "diffusers" +version = "0.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/81/6095237b86a3116c4789f28c4435d5296c00c0fc74ffde99008fd6b3a36c/diffusers-0.39.0.tar.gz", hash = "sha256:14bb1d98c85a0e463d734c99aaa73b480a7bc9bad22af30fbf730ef8f09c1d67", size = 4651240, upload-time = "2026-07-03T08:48:47.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/3f/7469c46e9d22307ea686bab687d70e6bf328722952f9d10339f5e913e608/diffusers-0.39.0-py3-none-any.whl", hash = "sha256:912aca51b5787365110806e984d5555735bf8a461073bb8459029d0bca7870ef", size = 5631176, upload-time = "2026-07-03T08:48:45.337Z" }, +] + +[[package]] +name = "dill" +version = "0.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/31/54dd222e02311c2dbc9e680d37cbd50f4494ce1ee9b04c69980e4ec26f38/dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03", size = 183355, upload-time = "2023-07-22T22:18:46.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/3a/74a29b11cf2cdfcd6ba89c0cecd70b37cd1ba7b77978ce611eb7a146a832/dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e", size = 115254, upload-time = "2023-07-22T22:18:44.511Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "etuples" +version = "0.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cons" }, + { name = "multipledispatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/c0/ba049efa7d216221713cffc303641bd73bbb309ff0e4e2a623f32af2a4ea/etuples-0.3.10.tar.gz", hash = "sha256:26fde81d7e822837146231bfce4d6ba67eab5d7ed55bc58ba7437c2568051167", size = 21493, upload-time = "2025-07-14T18:49:35.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/19/bf11636df040a9f9c3fd6959aedea5b5cfddd751272732278fb04ee0a78c/etuples-0.3.10-py3-none-any.whl", hash = "sha256:4408c7940ef06af52dbbea0954a8a1817ed5750ce905ff48091ac3cd3aeb720b", size = 12201, upload-time = "2025-07-14T18:49:34.557Z" }, +] + +[[package]] +name = "evaluate" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "datasets" }, + { name = "dill" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/d0/0c17a8e6e8dc7245f22dea860557c32bae50fc4d287ae030cb0e8ab8720f/evaluate-0.4.6.tar.gz", hash = "sha256:e07036ca12b3c24331f83ab787f21cc2dbf3631813a1631e63e40897c69a3f21", size = 65716, upload-time = "2025-09-18T13:06:30.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/af/3e990d8d4002bbc9342adb4facd59506e653da93b2417de0fa6027cb86b1/evaluate-0.4.6-py3-none-any.whl", hash = "sha256:bca85bc294f338377b7ac2f861e21c308b11b2a285f510d7d5394d5df437db29", size = 84069, upload-time = "2025-09-18T13:06:29.265Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fastapi" +version = "0.140.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/cb/7a4d2c2eb5a5d8a91763c05b7383d72917862e32f780daa0e27ffbb34cc6/fastapi-0.140.13.tar.gz", hash = "sha256:500172a08cf1459901f90b05c37d93060dada3b573fec8f0862445db52ba6b4b", size = 424843, upload-time = "2026-07-28T15:37:00.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4e/f9e8c762ef5e05c40482131e3d5e8b36bca13fa127578261f1d6b35a25d4/fastapi-0.140.13-py3-none-any.whl", hash = "sha256:8b017110e1e9f30a95e8bdb8f71fbe2f0fe3af5717109e5b14f9e069df54f6d4", size = 131222, upload-time = "2026-07-28T15:37:02.124Z" }, +] + +[package.optional-dependencies] +all = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/eb/3b534c6f8e157f9ddbf2a153512307c886cad0b258739c200dd8ff8c4452/fastapi_cli-0.0.32.tar.gz", hash = "sha256:38024d2345275e1b37ce8848727a580d84901b570e96b3256d9d36a9a5039424", size = 26636, upload-time = "2026-07-16T12:16:58.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/53/56ae5ae17bb0a5d89d1d31e5320eb1865553ebbfbde91cdc4c221245f2a8/fastapi_cli-0.0.32-py3-none-any.whl", hash = "sha256:8dcc286fa32f01bbd3f65dd09cfd5a2540ed5f2230b77db7fd30978d6165f3c4", size = 14670, upload-time = "2026-07-16T12:16:57.297Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "fastapi-cloud-cli" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "detect-installer" }, + { name = "fastar" }, + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "rich-toolkit" }, + { name = "rignore" }, + { name = "sentry-sdk" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dc/63aaf9913f455e39a7027c27140edd887a87d47d65ac43532d77a51718e5/fastapi_cloud_cli-0.23.0.tar.gz", hash = "sha256:840895bb8d14309aeffc905e0dcd1334d18c6f5da54b735413a8f1cb385e581e", size = 95295, upload-time = "2026-07-28T14:03:33.463Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/96/7e9aba6fabce3cb05f320abeef5b81efd5134823ae85d1a517872cb83cbc/fastapi_cloud_cli-0.23.0-py3-none-any.whl", hash = "sha256:1cd2ffa56e92e92c1fc63acc426c214dd928cbeed2a4c7c6a9a5fc85ea73de16", size = 78058, upload-time = "2026-07-28T14:03:34.386Z" }, +] + +[[package]] +name = "fastar" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/0f/0aeb3fc50046617702acc0078b277b58367fd62eb727b9ec733ae0e8bbcc/fastar-0.11.0.tar.gz", hash = "sha256:aa7f100f7313c03fdb20f1385927ba95671071ba308ad0c1763fef295e1895ce", size = 70238, upload-time = "2026-04-13T17:11:17.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4a/0d79fe52243a4130aa41d0a3a9eea22e00427db761e1a6782ee817c50222/fastar-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7c906ad371ca365591ebcb7630009923f3eceb20956814494d15591a78e9e46", size = 709786, upload-time = "2026-04-13T17:09:53.974Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e4/77c94eaafc035e39f5ce5176e32743da4e3fe890f28790e708e53d8f75cd/fastar-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6919497b35fa5bd978d2c26ee117cf1771b90ee5073f7518e44b9bc364b57715", size = 632127, upload-time = "2026-04-13T17:09:39.023Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f6/97658dd992f4e45747d35adb24c0b100f6b6d451490685ae3fe8a3a2ee1b/fastar-0.11.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:56b50206aeedd99e22b83289e6fb3ff8f7d7da4407d2419902e4716b4f90585a", size = 869608, upload-time = "2026-04-13T17:09:08.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fc/81c1ec4d8146a437399e7b95631b51be312f323a9ce64569f932db6c3914/fastar-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a1811a69ae81d469720df0c8af3f84f834a93b5e4f8be0e0e8bde6a52fa11f2", size = 762925, upload-time = "2026-04-13T17:07:52.788Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/49baf480ecb197aea7ce2515c503a2f25061958dd3b4c98e98a3a11cdcc7/fastar-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10486238c55589a3947c38f9cfb88a67d8a608eb8dddc722038237d0278a41d7", size = 759913, upload-time = "2026-04-13T17:08:07.324Z" }, + { url = "https://files.pythonhosted.org/packages/94/eb/946f1980267f2824efb7d7c518d47a49b89c0e9cd7c449301f5a7531558a/fastar-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1555ef9992d368a6ec39092276990cef8d329c39a1d86ebd847eaa3b10efd472", size = 926054, upload-time = "2026-04-13T17:08:22.196Z" }, + { url = "https://files.pythonhosted.org/packages/0c/19/d5eb611085ce054382570d8d4e24a5e2ff23cd6d2404528a6643841d6059/fastar-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1f4aca0a9620b76988bbf6225cdea6678a392902444ca18bb8a51495b165a89", size = 818594, upload-time = "2026-04-13T17:08:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/4a/52/18e8d55c0d3d917713f381cb2d0cb793da00c209c802e011d8dc72018cd5/fastar-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75beeecac7d11a666a6c4a0b7f7e80842ae5cf523f2f890b99c78fc82b403545", size = 823005, upload-time = "2026-04-13T17:09:23.051Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/0fecdcf33e5aaffe777b96a1c10a3204fe0b05bf18e971033a0bfedafc1c/fastar-0.11.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a08cdf5d16daa401c65c9c7493a18db7dc515c52155a17071ec7098bb07da9d3", size = 887115, upload-time = "2026-04-13T17:08:37.385Z" }, + { url = "https://files.pythonhosted.org/packages/08/f8/2a6ad1c2523eb72a4595a9331162fc67ce0f0aee3348728598026c516986/fastar-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6e210375e5a7ba53586cbd6017aa417d2d2ceacbe8671682470281bd0a15e8ef", size = 973595, upload-time = "2026-04-13T17:10:09.258Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/2aa48843228673feacc2b80876b8924e63ea9c5f5f607bd7a72416b86bae/fastar-0.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a2988eb2604b8e15670f355425e8c800e4dcd4edfbcbfe194397f8f17b7eb19e", size = 1036988, upload-time = "2026-04-13T17:10:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/92/ac/3dd14b21c323e8484f47c910110d1d93139ba44621ac2c4c597dbe9fcdb7/fastar-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34abc857b46068fdf91d157bd0203bfd6791dc7a432d1ed180f5af6c2f5bcce9", size = 1078267, upload-time = "2026-04-13T17:10:43.645Z" }, + { url = "https://files.pythonhosted.org/packages/de/a1/3f89e58d6fa99160c9e7e17220c8ab5040b5cc017c4fac2356c6ed18453d/fastar-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d884be84e37a01053776395441fc960031974e0265801ce574efc3d05e0cdaf", size = 1032551, upload-time = "2026-04-13T17:11:00.667Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ea/24dd3cfc2096933d7d2a80c926e79602cff1fa481124ed2165b60c1dd9ef/fastar-0.11.0-cp310-cp310-win32.whl", hash = "sha256:c721c1ad758e3e4c2c1fd9e96911a0fa58c0a6be5668f1bcfd0b741e72c7cb63", size = 456022, upload-time = "2026-04-13T17:11:41.859Z" }, + { url = "https://files.pythonhosted.org/packages/82/ef/6eb39ee9cdd59822d1c7337c4d28fdc948885bdf455af9e70efa9879e06f/fastar-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba4180b7c3080f55f9035fdd7d8c39fe0e1485087a68ff615bb4784a10b8106b", size = 488392, upload-time = "2026-04-13T17:11:27.486Z" }, + { url = "https://files.pythonhosted.org/packages/11/7a/fb367bdaf4efa2c7952a45aeab2e87a564293ecffe150af673ec8edfda46/fastar-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b82fd6f996e65a86f67a6bd64dd22ef3e8ae2dcaed0ae3b550e71f7e1bbb1df5", size = 709869, upload-time = "2026-04-13T17:09:55.62Z" }, + { url = "https://files.pythonhosted.org/packages/80/ff/b87efb0dcfd081c62c7c7601d7681dabe63103cd51fc16f8d57a1ab45961/fastar-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27eed386fd0558e6daa29211111bbd7b740f7c7e881197f8a00ac7c0f3cdb1d7", size = 631668, upload-time = "2026-04-13T17:09:40.537Z" }, + { url = "https://files.pythonhosted.org/packages/24/7c/0ed6dd38b9adc04b3a8ec3b7045908e7c2170ba0ff6e6d2c51bc9fc770f3/fastar-0.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a6931bebc1d8e95ddeef55732c195449e6b44ef33aa31b325505097ed3b4d6aa", size = 869663, upload-time = "2026-04-13T17:09:09.78Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/8b7fb3f23855accebaaf2d2637eac7f261a7a5d936f861a172079f1ef511/fastar-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f72ce42a5e28a74fbd4d5fbf1a3ac1a1163d13cbc200cbd005fb0fabc54bd", size = 762938, upload-time = "2026-04-13T17:07:54.51Z" }, + { url = "https://files.pythonhosted.org/packages/07/cc/5491e2b677bb841f768e3aba052d0344338a5c78aa5d4c18b443831a8e8d/fastar-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5b83c1f61f7017d6e1498568038f8745440cfc16ca2f697ec81bac83050108f6", size = 759232, upload-time = "2026-04-13T17:08:08.864Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/643630bdbd179e41e9fae31c03b4cf6061dbf4d6fbbae8425d16eb12545d/fastar-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db73a9b765a516e73983b25341e7b5e0189733878279e278b2295131b0e3a21e", size = 926271, upload-time = "2026-04-13T17:08:23.68Z" }, + { url = "https://files.pythonhosted.org/packages/09/5d/37ade50003b4540e0a53ef100f6692d7ab2ac1122d5acf39920cc09a3e8b/fastar-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:625827d52eb4e8fec942e0233f125ff8010fcf6a67c0a974a8e5f4666b771e3c", size = 818634, upload-time = "2026-04-13T17:08:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ff/135d177de32cc1e837c99019e4643e6e79352bde49544d4ece5b5eebf56b/fastar-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7f5fd8fa21ec0a88296a38dc5d7fc35efd3b26d46a17b8b7c73c5563925ca15", size = 822755, upload-time = "2026-04-13T17:09:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/27/cb/b835dbe76ceac7fa6105851468c259ffd06830eb9c029402e499d0ec153b/fastar-0.11.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8c15af91b8cd87ddf23ea55355ae513c1de3ab67178f26dad017c9e9c0af6096", size = 887101, upload-time = "2026-04-13T17:08:39.248Z" }, + { url = "https://files.pythonhosted.org/packages/9e/54/aa8289eb57fc550535470397cb051f5a58a7c89ca4de31d5502b916dd894/fastar-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a112395a8b0bff251423bd1564c012f0cc058ad8b6bd8fba96f3d7fc117e44", size = 973606, upload-time = "2026-04-13T17:10:10.98Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/776d50a0897c01dc6bfd0926772ee913436fdae91b9affaf0a0cbd09f0a1/fastar-0.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f2994bb8f5f8c11eb12beae1e6e77a907173c9819236b8a4c8f0573652ceccce", size = 1036696, upload-time = "2026-04-13T17:10:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f1/cf0f9b499fb37ac065c8a01ec642f96a3c5eb849c38ae983b59f3b3245e0/fastar-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dcf99e4b5973d842c7f19c776c3a83cdc0977d505edce6206438505c0456b517", size = 1078182, upload-time = "2026-04-13T17:10:45.318Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9e/21e4701aec4a1123d4dc4d31578dc18875582b5710e4725f7ceb752a248b/fastar-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29c9c386dc0d5dda78845a8e6b1480d26ab861c1e0b68f42ae5735cb70ca07f1", size = 1032336, upload-time = "2026-04-13T17:11:02.364Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/5872b28c72c27ec1a00760eace6ff35f714f41ebbd5208cf016b12e29250/fastar-0.11.0-cp311-cp311-win32.whl", hash = "sha256:030b2580fc394f2c9b7890b6735810404e9b9ed5e0344db150b945965b5482b7", size = 457368, upload-time = "2026-04-13T17:11:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6e/ce6832a16193eb4466f4108be8809c249b51cb1f89dd7894545700d079d5/fastar-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:83ab57ae067969cd0b483ac3b6dccc4b595fc77f5c820760998648d4c42822b5", size = 488605, upload-time = "2026-04-13T17:11:29.161Z" }, + { url = "https://files.pythonhosted.org/packages/15/5a/9cfb80661cf38fd7b0889224beb7d2746784d4ade2a931ed9775a18d8602/fastar-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:27b1a4cee2298b704de8151d310462ee7335ed036011ca9aa6e784b30b6c73a9", size = 464580, upload-time = "2026-04-13T17:11:18.583Z" }, + { url = "https://files.pythonhosted.org/packages/0f/06/a5773706afc8bd496769786590bbc56d2d0ee419a299cc12ea3f5717fcf3/fastar-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3c51f1c2cdddbd1420d2897ace7738e36c65e17f6ae84e0bfe763f8d1068bb97", size = 708394, upload-time = "2026-04-13T17:09:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a6/d5e2a4e48495616440a21eed07558219ca90243ad00b0502586f95bd4833/fastar-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d9d6b052baf5380baea866675dab6ccd04ec2460d12b1c46f10ce3f4ee6a820", size = 628417, upload-time = "2026-04-13T17:09:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/ab/69/9816d69ac8265c9e50456637a487ccfb7a9c566efd9dbcd673df9c2558c2/fastar-0.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd2f05666d4df7e14885b5c38fefd92a785917387513d33d837ff42ec143a22f", size = 863950, upload-time = "2026-04-13T17:09:11.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0d/f88daad53aff2e754b6b5ff2a7113f72447a34f6ef17cc23ca99988117b7/fastar-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e6e74aba1ae77ca4aedcaf1697cd413319f4c88a5ccbe5b42c709517c5097e", size = 760737, upload-time = "2026-04-13T17:07:55.958Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a6/82ef4ecd969d50d92ed3ed9dbd8fe77faa24be5e5736f716edc9f4ce8d62/fastar-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38ef77fe940bbc9b37a98bd838727f844b11731cd39358a2640ff864fb385086", size = 757603, upload-time = "2026-04-13T17:08:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/03/35/50249f0d827251f8ac511495e2eacccebda80a00a0ad73e9615b8113b84f/fastar-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8955e61b32d6aff82c983217abf80933fd823b0e727586fc72f08043d996fd59", size = 923952, upload-time = "2026-04-13T17:08:25.526Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/faee41659e9c379d906d24eaee6d6833ac8cfef0a5df480e5c2a8d3efb33/fastar-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:483532442cdb08fbff0169510224eae0836f2f672cea6aacb52847d90fefdc46", size = 816574, upload-time = "2026-04-13T17:08:56.076Z" }, + { url = "https://files.pythonhosted.org/packages/22/47/0448ea7992b997dad2bf004bfd98eca74b5858630eae080b50c7b17d9ddc/fastar-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef5a6071121e05d8287fc75bccb054bcbac8bb0501200a0c0a8feeace5303ea4", size = 819382, upload-time = "2026-04-13T17:09:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/33/ef/0d63eb43586831b7a6f8b22c4d77125a7c594423af1f4f090fa9541b9b40/fastar-0.11.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e45e598af5afe8412197d4786efd6cf29be02e7d3d4f6a3461149eae5d7e94f1", size = 885254, upload-time = "2026-04-13T17:08:40.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/25/edd584675d69e49a165052c3ee886df1c5d574f3e7d813c990306387c623/fastar-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e160919b1c47ddb8538e7e8eb4cd527281b40f0bf75110a75993838ef61f286", size = 971239, upload-time = "2026-04-13T17:10:12.997Z" }, + { url = "https://files.pythonhosted.org/packages/a5/37/e8bb24f506ba2b08fbaf36c5800e843bd4d542954e9331f00418e2d23349/fastar-0.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4bb4dc0fc8f7a6807febcebce8a2f3626ba4955a9263d81ecc630aad83be84c0", size = 1035185, upload-time = "2026-04-13T17:10:30.207Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/be753736296338149ee4cb3e92e2b5423d6ba17c7b951d15218fd7e99bbf/fastar-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4ec95af56aa173f6e320e1183001bf108ba59beaf13edd1fc8200648db203588", size = 1072191, upload-time = "2026-04-13T17:10:47.072Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/a81c1aaafb5a22ce57c98ae22f39c89413ed53e4ee6e1b1444b0bd666a6c/fastar-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:136cf342735464091c39dc3708168f9fdeb9ebea40b1ead937c61afaf46143d9", size = 1028054, upload-time = "2026-04-13T17:11:04.293Z" }, + { url = "https://files.pythonhosted.org/packages/ec/88/1ce4eed3d70627c95f49ca017f6bbbf2ddcc4b0c601d293259de7689bc20/fastar-0.11.0-cp312-cp312-win32.whl", hash = "sha256:35f23c11b556cc4d3704587faacbc0037f7bdf6c4525cd1d09c70bda4b1c6809", size = 454198, upload-time = "2026-04-13T17:11:45.168Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1d/26ce92f4331cd61a69840db9ca6115829805eec24f285481a854f578e917/fastar-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:920bc56c3c0b8a8ca492904941d1883c1c947c858cd93343356c29122a38f44c", size = 486697, upload-time = "2026-04-13T17:11:31.084Z" }, + { url = "https://files.pythonhosted.org/packages/ed/96/e6eda4480559c69b05d466e7b5ea9170e81fef3795a73e059959a3258319/fastar-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:395248faf89e8a6bd5dc1fd544c8465113b627cb6d7c8b296796b60ebea33593", size = 462591, upload-time = "2026-04-13T17:11:20.577Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d6/3be260037e86fb694e88d47f583bac3a0188c99cee1a6b257ac26cb6b53c/fastar-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:33f544b08b4541b678e53749b4552a44720d96761fb79c172b005b1089c443ed", size = 707975, upload-time = "2026-04-13T17:09:58.866Z" }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7867aefb1784662554a335f2952c75a50f0c70585ed0d2210d6cc15e5627/fastar-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c1c792447e4a642745f347ff9847c52af39633071c57ee67ed53c157fc3506", size = 628460, upload-time = "2026-04-13T17:09:43.776Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/d11d84bdd5e0e377771b955755771e3460b290da5809cb78c1b735ee2228/fastar-0.11.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:881247e6b6eaea59fc6569f9b61447aa6b9fc2ee864e048b4643d69c52745805", size = 863054, upload-time = "2026-04-13T17:09:13.048Z" }, + { url = "https://files.pythonhosted.org/packages/25/39/d3f428b318fa940b1b6e785b8d54fc895dfb5d5b945ef8d5442ffa904fb2/fastar-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:863b7929845c9fec92ef6c8d59579cf46af5136655e5342f8df5cebe46cab06c", size = 760247, upload-time = "2026-04-13T17:07:57.396Z" }, + { url = "https://files.pythonhosted.org/packages/9e/04/03949aee82aabb8ede06ac5a4a5579ffaf98a8fe59ce958494508ff15513/fastar-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96b4a57df12bf3211662627a3ea29d62ecb314a2434a0d0843f9fc23e47536e5", size = 756512, upload-time = "2026-04-13T17:08:12.415Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0c/2ca1ae0a3828ca51047962d932b80daca2522db73e8cb9d040cb6ebe28d5/fastar-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceef1c2c4df7b7b8ebd3f5d718bbf457b9bbdf25ce0bd07870211ec4fbd9aff4", size = 922183, upload-time = "2026-04-13T17:08:27.187Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/7fe808b1f73a68e686f25434f538c6dc10ef4dfb3db0ace22cd861744bf8/fastar-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8e545918441910a779659d4759ad0eef349e935fbdb4668a666d3681567eb05", size = 816394, upload-time = "2026-04-13T17:08:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/1f/17/07d086080f8a83b8d7966955e29bcdbd6a060f5bd949dc9d5abd3658cead/fastar-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28095bb8f821e85fc2764e1a55f03e5e2876dee2abe7cd0ee9420d929905d643", size = 818983, upload-time = "2026-04-13T17:09:28.46Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e2/2c4edf0910af2e814ff6d65b77a91196d472ca8a9fb2033bd983f6856caa/fastar-0.11.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0fafb95ecbe70f666a5e9b35dd63974ccdc9bb3d99ccdbd4014a823ec3e659b5", size = 884689, upload-time = "2026-04-13T17:08:42.763Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/04fdcbd6558e60de4ced3b55230fac47675d181252582b2fcec3c74608e5/fastar-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af48fed039b94016629dcdad1c95c90c486326dd068de2b0a4df419ee09b6821", size = 970677, upload-time = "2026-04-13T17:10:15.124Z" }, + { url = "https://files.pythonhosted.org/packages/df/b3/2b860a9658550167dbd5824c85e88d0b4b912bf493e42a6322544d6e483d/fastar-0.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:74cd96163f39b8638ab4e8d49708ca887959672a22871d8170d01f067319533b", size = 1034026, upload-time = "2026-04-13T17:10:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/b7/9b/fa42ea1188b144bac4b1b60753dfd449974a4d5eda132029ee7711569f94/fastar-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e8b993cb5613bab495ed482810bedc0986633fcb9a3b55c37ec88e0d6714f6a", size = 1071147, upload-time = "2026-04-13T17:10:48.833Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/d2e501556dca9f1fbc9246111a31792fb49ad908fa4927f34938a97a3604/fastar-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfe39d91fc28e37e06162d94afe01050220edb7df554acb5b702b5503e564816", size = 1028377, upload-time = "2026-04-13T17:11:06.374Z" }, + { url = "https://files.pythonhosted.org/packages/db/33/5f11f23eca0a569cd052507bc45dda2e5468697f8665728d25be44120f7d/fastar-0.11.0-cp313-cp313-win32.whl", hash = "sha256:c5f63d4d99ff4bfb37c659982ec413358bdee747005348756cc50a04d412d989", size = 454089, upload-time = "2026-04-13T17:11:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/35ff03c939cba7a255a9132367873fec6c355fd06a7f84fedcbaf4c8129f/fastar-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8690ed1928d31ded3ada308e1086525fb3871f5fa81e1b69601a3f7774004583", size = 486312, upload-time = "2026-04-13T17:11:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/ee9246cbfcbfd4144558f35e7e9a306ffe0a7564730a5188c45f21d2dab8/fastar-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:d977ded9d98a0719a305e0a4d5ee811f1d3e856d853a50acb8ae833c3cd6d5d2", size = 461975, upload-time = "2026-04-13T17:11:22.589Z" }, + { url = "https://files.pythonhosted.org/packages/7a/cd/3644c48ecac456f928c12d47ec3bed36c36555b17c3859856f1ff860265d/fastar-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:71375bd6f03c2a43eb47bd949ea38ff45434917f9cdac79675c5b9f60de4fa73", size = 707860, upload-time = "2026-04-13T17:10:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/69/ca/dee04476ae3626b2b040a60ad84628f77e1ffd8444232f2426b0ca1e0d7e/fastar-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:eddfd9cab16e19ae247fe44bf992cb403ccfe27d3931d6de29a4695d95ad386c", size = 628216, upload-time = "2026-04-13T17:09:45.355Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5e/9395c7353d079cb4f5be0f7982ce0dc9f2e7dec5fd175eef466729d6023a/fastar-0.11.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c371f1d4386c699018bb64eb2fa785feacf32785559049d2bb72fe4af023f53", size = 864378, upload-time = "2026-04-13T17:09:14.611Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/1e4f67148223ff219612b6281a6000357abbcc2417964fa5c83f11d68fce/fastar-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cad7fa41e3e66554387481c1a09365e4638becd322904932674159d5f4046728", size = 760921, upload-time = "2026-04-13T17:07:59.138Z" }, + { url = "https://files.pythonhosted.org/packages/0f/82/09d11fb6d12f17993ffaf32ffd30c3c121a11e2966e84f19fb6f66430118/fastar-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf36652fa71b83761717c9899b98732498f8a2cb6327ff16bbf07f6be85c3437", size = 757012, upload-time = "2026-04-13T17:08:14.186Z" }, + { url = "https://files.pythonhosted.org/packages/52/1f/5aeeacc4cb65615e2c9292cd9c5b0cd6fb6d2e6ee472ca6adc6c1b1b22ef/fastar-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f68ff8c17833053da4841720e95edde80ce45bb994b6b7d51418dddaac70ee47", size = 924510, upload-time = "2026-04-13T17:08:28.741Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1a/1e5bdabbeaf2e856928956292609f2ff6a650f94480fb8afaca30229e483/fastar-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4563ed37a12ea1cdc398af8571258d24b988bf342b7b3bf5451bd5891243280c", size = 816602, upload-time = "2026-04-13T17:08:59.461Z" }, + { url = "https://files.pythonhosted.org/packages/87/24/f960147910da3bed41a3adfcb026e17d5f50f4cf467a3324237a7088f61a/fastar-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cee63c9875cba3b70dc44338c560facc5d6e763047dcc4a30501f9a68cf5f890", size = 819452, upload-time = "2026-04-13T17:09:29.926Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f4/3e77d7901d5707fd7f8a352e153c8ae09ea974e6fabad0b7c4eb9944b8d4/fastar-0.11.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:bd76bfffae6d0a91f4ac4a612f721e7aec108db97dccdd120ae063cd66959f27", size = 885254, upload-time = "2026-04-13T17:08:44.285Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/1585edd5ec47782ae93cd94edf05828e0ab02ef00aec00aea4194a600464/fastar-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f5b707501ec01c1bc0518f741f01d322e50c9adc19a451aa24f67a2316e9397", size = 971496, upload-time = "2026-04-13T17:10:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e9/6874c9d1236ded565a0bed54b320ac9f165f287b1d89490fb70f9f323c81/fastar-0.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:37c0b5a88a657839aad98b0a6c9e4ac4c2c15d6b49c44ee3935c6b08e9d3e479", size = 1034685, upload-time = "2026-04-13T17:10:34.063Z" }, + { url = "https://files.pythonhosted.org/packages/14/d8/4ab20613ce2983427aee958e39be878dba874aa227c530a845e32429c4f6/fastar-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6c55f536c62a6efb180c1af0d5182948bff576bbfe6276e8e1359c9c7d2215d8", size = 1072675, upload-time = "2026-04-13T17:10:50.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/5ac3b7c20ce4b08f011dd2b979f96caabe64f9b10b157f211ea91bdfadca/fastar-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3082eeca59e189b9039335862f4c2780c0c8871d656bfdf559db4414a105b251", size = 1029330, upload-time = "2026-04-13T17:11:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e7/37cd6a1d4e288292170b64e19d79ecce2a7de8bb76790323399a2abc4619/fastar-0.11.0-cp314-cp314-win32.whl", hash = "sha256:b201a0a4e29f9fec2a177e13154b8725ec65ab9f83bd6415483efaa2aa18344b", size = 453940, upload-time = "2026-04-13T17:11:48.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1c/795c878b1ee29d79021cf8ed81f18f2b25ccde58453b0d34b9bdc7e025ea/fastar-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:868fddb26072a43e870a8819134b9f80ee602931be5a76e6fb873e04da343637", size = 486334, upload-time = "2026-04-13T17:11:34.882Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a4/113f104301df8bddcc0b3775b611a30cb7610baa3add933c7ccac9386467/fastar-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:3db39c9cc42abb0c780a26b299f24dfbc8be455985e969e15336d70d7b2f833b", size = 461534, upload-time = "2026-04-13T17:11:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/5c5f2c2c8e0c63e56a5636ebc7721589c889e94c0092cec7eb28ae7207e6/fastar-0.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:49c3299dec5e125e7ebaa27545714da9c7391777366015427e0ae62d548b442b", size = 707156, upload-time = "2026-04-13T17:10:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/df/f7/982c01b61f0fc135ad2b16d01e6d0ee53cf8791e68827f5f7c5a65b2e5b1/fastar-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3328ed1ed56d31f5198350b17dd60449b8d6b9d47abb4688bab6aef4450a165b", size = 627032, upload-time = "2026-04-13T17:09:46.978Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c3/38f1dac77ae0c71c37b176277c96d830796b8ce2fe69705f917829b53829/fastar-0.11.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd3eca3bbfec84a614bcb4143b4ad4f784d0895babc26cfc88436af88ca23c7a", size = 864403, upload-time = "2026-04-13T17:09:16.58Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/e69c363bdb3e5a5848e937b662b5469581ee6682c51bc1c0556494773929/fastar-0.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff86a967acb0d621dd24063dda090daa67bf4993b9570e97fe156de88a9006ca", size = 759480, upload-time = "2026-04-13T17:08:00.599Z" }, + { url = "https://files.pythonhosted.org/packages/3b/29/4d8737590c2a6357d614d7cc7288e8f68e7e449680b8922997cc4349e65e/fastar-0.11.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86eaf7c0e985d93a7734168be2fb232b2a8cca53e41431c2782d7c12b12c03b1", size = 756219, upload-time = "2026-04-13T17:08:15.699Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ec/400de7b3b7d48801908f19cf5462177104395799472671b3e8152b2b04ca/fastar-0.11.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91f07b0b8eb67e2f177733a1f884edad7dfb9f8977ffef15927b20cb9604027d", size = 923669, upload-time = "2026-04-13T17:08:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/5d/01/8926c53da923fed7ab4b96e7fbf7f73b663beb4f02095b654d6fab46f9ad/fastar-0.11.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f85c896885eb4abf1a635d54dea22cac6ae48d04fc2ea26ae652fcf1febe1220", size = 815729, upload-time = "2026-04-13T17:09:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/89/f0/5fef4c7946e352651b504b1a4235dac3505e7cfd24020788ab50552e84bf/fastar-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:075c07095c8de4b774ba8f28b9c0a02b1a2cd254da50cbe464dd3bb2432e9158", size = 819812, upload-time = "2026-04-13T17:09:31.907Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c8/0ebc3298b4a45e7bddc50b169ae6a6f5b80c939394d4befe6e60de535ee7/fastar-0.11.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:07f028933820c65750baf3383b807ecce1cd9385cf00ce192b79d263ad6b856c", size = 884074, upload-time = "2026-04-13T17:08:45.802Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9f/7baa4cdff8d6fbca41fa5c764b48a941fed8a9ec6c4cc92de65895a28299/fastar-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:039f875efa0f01fa43c20bf4e2fc7305489c61d0ac76eda991acfba7820a0e63", size = 969450, upload-time = "2026-04-13T17:10:18.667Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/1ebbfb58a47056ba866494f19efbcdd2ba2897096b94f36e796594b4d05b/fastar-0.11.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fff12452a9a5c6814a012445f26365541cc3d99dcca61f09762e6a389f7a32ea", size = 1033775, upload-time = "2026-04-13T17:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/c2/5f/ce4e3914066f08c99eb8c32952cc07c1a013e81b1db1b0f598130bf6b974/fastar-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2bf733e09f942b6fa876efe30a90508d1f4caef5630c00fb2a84fba355873712", size = 1072158, upload-time = "2026-04-13T17:10:52.497Z" }, + { url = "https://files.pythonhosted.org/packages/03/2a/6bca72992c84151c387cc6558f3867f5ebe5fb3684ee6fa9b76280ba4b8e/fastar-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d1531fa848fdd3677d2dce0a4b436ea64d9ae38fb8babe2ddbc180dd153cb7a3", size = 1028577, upload-time = "2026-04-13T17:11:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/83/18/7a7c15657a3da5569b26fc51cde6a80f8d84cb54b3b1aea6d74a103db4ad/fastar-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:5744551bc67c6fc6581cbd0e34a0fd6e2cd0bd30b43e94b1c3119cf35064b162", size = 453601, upload-time = "2026-04-13T17:11:53.726Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/331b59a6de279f3ad75c10c02c40a12f21d64a437d9c3d6f1af2dcbd7a76/fastar-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f4ce44e3b56c47cf38244b98d29f269b259740a580c47a2552efa5b96a5458fb", size = 486436, upload-time = "2026-04-13T17:11:40.089Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fd/5390ec4f49100f3ecb9968a392f9e6d039f1e3fe0ecd28443716ff01e589/fastar-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:76c1359314355eafbc6989f20fb1ad565a3d10200117923b9da765a17e2f6f11", size = 461049, upload-time = "2026-04-13T17:11:25.918Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5c/9bbeffbf1905391446dd98aa520422ce7affde5c9a7c22d757cc5d7c1397/fastar-0.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1266d6a004f427b0d61bd6c7b544d84cc964691b2232c2f4d635a1b75f2f6d5e", size = 711644, upload-time = "2026-04-13T17:10:07.663Z" }, + { url = "https://files.pythonhosted.org/packages/7e/af/ae5cf39d4fb82d0c592705f5ec6db1b065be5265c151b108f86126ee8773/fastar-0.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:298a827ec04ade43733f6ca960d0faec38706aa1494175869ea7ea17f5bad5d3", size = 634371, upload-time = "2026-04-13T17:09:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/7e/36/8d4569e26473c72ccb02d1c5df3ed710073f1c06eca09c26d52ea79fd815/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8800e2387e463a0e5799416a1cbe72dd0fde7270a20e4bde684145e7878f6516", size = 870850, upload-time = "2026-04-13T17:09:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/724dc796e1756d3977970f820d30d59bb8cab8e3671b285f1d82ab513aec/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7496def0a2befd82d429cb004ef7ca831585cc887947bd6b9abb68a5ef852b0b", size = 764469, upload-time = "2026-04-13T17:08:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/99/e3/74d6859e632e8fb9339a14f652fb9f800c2bd6aa53071e311c0be3fbab8b/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878eaf15463eb572e3538af7ca3a8534e5e279cf8196db902d24e5725c4af86e", size = 761375, upload-time = "2026-04-13T17:08:20.669Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e7/cc70e2be5ef8731a7525552b1c35c1448cf9eae6a62cb3a56f12c1bf27ea/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0324ed1d1ef0186e1bbd843b17807d6d837d0906899d4c99378b02c5d86bdd9c", size = 928189, upload-time = "2026-04-13T17:08:35.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/33/c9a969e78dca323547276a6fee5f4f9588f7cd5ab45acec3778c67399589/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdf9bd863205590beaf8ef6e66f315310196632180dceaf674985d01a876cac3", size = 820864, upload-time = "2026-04-13T17:09:06.366Z" }, + { url = "https://files.pythonhosted.org/packages/84/bd/6b9434b541fe55c125b5f2e017a565596a2d215aa09207e4555e4585064f/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59af8dbb683b24b90fb5b506de080faeab0a17a908e6c2a5d93a97260ed75d7b", size = 824060, upload-time = "2026-04-13T17:09:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/24/8d/871d5f8cf4c6f13987119fb0a9ae8be131e34f2756c2524e9974adf33824/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:9f3df73a3c4292cfe15696cdf59cdb6c309ab59d30b34c733be13c6e32d9a264", size = 889217, upload-time = "2026-04-13T17:08:50.884Z" }, + { url = "https://files.pythonhosted.org/packages/d0/26/cca0fd2704f3ed20165e5613ed911549aef3aaf3b0b5b02fee0e8e23e6cc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa3762cbb16e41a76b61f4a6914937a71aab3a7b6c2d82ca233bc686ebaf756b", size = 975418, upload-time = "2026-04-13T17:10:24.307Z" }, + { url = "https://files.pythonhosted.org/packages/99/94/8bbb0b13f5b6cbe2492f0b7cbba5103e6163976a3331466d010e781fa189/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a8c7bc8ac74cb359bb546b199288c83236372d094b402e557c197e85527495cd", size = 1038492, upload-time = "2026-04-13T17:10:41.939Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d3/5b7df222a30eac2822ffd00f82fd4c2ce84fba4b369d1e1a03732fd177fc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:587cbd060a2699c5f66281081395bb4657b2b1e0eef5c206b1aabf740019d670", size = 1080210, upload-time = "2026-04-13T17:10:58.462Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/56ef943ea524784598c035ccbd42e564e937da0438ae3f55f0e76cb95571/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a1c56957ac82408be37a3f63594bc83e0919e8760492a4475e542f9f1828778", size = 1034886, upload-time = "2026-04-13T17:11:15.617Z" }, +] + +[[package]] +name = "fasteners" +version = "0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "grad-cam" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "scikit-learn" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "tqdm" }, + { name = "ttach" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b3/e8b060e69d4de4b4d8a86868762dbc1ecaa58affa538a8af201a38a408ef/grad-cam-1.5.5.tar.gz", hash = "sha256:690c433d226d35c89c9eb170462db204909cb06b39c7381e6880a49b6fc37015", size = 7783293, upload-time = "2025-04-07T05:13:54.984Z" } + +[[package]] +name = "greenery" +version = "3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/c8/9c6989c871b77b093482e641daec9d4a0a45357fa0f2ed3ffa3102a94e7e/greenery-3.2.tar.gz", hash = "sha256:bbfa4fb50316f08fe36e20f98d95115f6006102b73f31d106ff6c9f5ae8cb3a7", size = 43098, upload-time = "2020-06-25T19:20:13.707Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/c8/46a894b803f900489146d7290833e2a43ff1a6e7680f784ae9ff8f591865/greenery-3.2-py3-none-any.whl", hash = "sha256:10099a38c18620b999a2ec647fa5e40c63cac6b63a8fd6b7f92c959623f39d16", size = 40588, upload-time = "2020-06-25T19:20:12.153Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/74/b13368064b09053253555d3f2839cc2684d22d5aed0d2ccffbf7a6736558/greenlet-3.5.4.tar.gz", hash = "sha256:0232ae1de90a8e07867bb127d7a6ba2301e859145489f25cda8a6096dabe1d20", size = 206538, upload-time = "2026-07-22T12:47:14.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/9d/58f80897f4121f5c218bb931cf6d3b6514873f02ad0b729f744352926b9f/greenlet-3.5.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:ac5bf81d79d2c8eeb2ef6359b2e1687a1e9ebf46c2b1f970da9a9255df51d190", size = 293072, upload-time = "2026-07-22T11:38:14.299Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9f/b4bc9bbd6a7855cbd8ad8a83c874eeeca56c24de9132b3323f81c03a30ba/greenlet-3.5.4-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89f3738167bab8c1084b94e23023d41d247117ac149fa0fbcb5bd4cf6262b353", size = 609393, upload-time = "2026-07-22T12:26:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/744b5e063af127d2e3c74fe0f1aef15573064c83b6066883524f5b258b17/greenlet-3.5.4-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9a5e3406e3ed8125ae1a3b37c12f3434e2b1f0fa053197c5557895b4fb09606", size = 622750, upload-time = "2026-07-22T12:28:59.546Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5c/53d6b94742a6f1ee1877c7ff76262c909e137f3f7383ce96a8ab78e1ae31/greenlet-3.5.4-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a2d614cb2372c7101a12ea8b96dd56f81c986d247c5a73db67063f3ed1ca4a52", size = 629659, upload-time = "2026-07-22T12:43:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6b/d78ea2908e8e08985348f28ac396c2950be7ab66321dfe0054c73bd1f456/greenlet-3.5.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ab9f0704bccf6d3b38e0d2130b7b33271cff11453690da074fa280c3aa8e8e7", size = 622920, upload-time = "2026-07-22T11:51:06.83Z" }, + { url = "https://files.pythonhosted.org/packages/f2/34/957fc5577180ef2f57be82580ee1f59fdefad4f6c623c7d5e1b6980a76fb/greenlet-3.5.4-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:188e4d142f243051d92a1f5c244a741da02dddc070a0620c842804d7b56d008c", size = 425580, upload-time = "2026-07-22T12:39:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/3ce7009c948920b01527f8d9da29f501a31ac3d98318829e981fd879b850/greenlet-3.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2cdaadc3d31445a8f782bde3cd37e49a2c2a9c6da6daf76a3e34c683b271a3c7", size = 1582262, upload-time = "2026-07-22T12:25:01.131Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4c/0408366102a33829f7bdd6a992dad75abbf75e86cc1e76caf19e57311d29/greenlet-3.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70bdfacdc183dac838b2a0aaff2dd6134a457c52fe68a9c6bbab435483d2b9df", size = 1648906, upload-time = "2026-07-22T11:51:08.627Z" }, + { url = "https://files.pythonhosted.org/packages/13/52/ebfe8f6a1aeb8e430540b406c844ecc4e3367072b0192f69dcb85eeeec2b/greenlet-3.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:69173331fbc5d64bfac0065d7e22c39cfcd089e9b18d125bdcd5079363b09616", size = 246036, upload-time = "2026-07-22T11:38:30.073Z" }, + { url = "https://files.pythonhosted.org/packages/61/16/71eefcf68267bbf06a9b6bff57d0b222e49432326e85d74348b67694b8d4/greenlet-3.5.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e883de250e299654b1f1680f72a1a9f9ba62c9bd1bce84099c90657349a8dfbb", size = 294266, upload-time = "2026-07-22T11:37:56.142Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/a0b19adfc35d07e10acb626e9d22a3893b95f1309c42c4a20161dec16800/greenlet-3.5.4-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32802705c2c1ff25e8237b3bdacf2594fa02be80af8a66703eb7853ea7e68686", size = 613712, upload-time = "2026-07-22T12:26:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a121978b3337407d05a1ce5f79b4aa5998a43a9d8422f9726029b90b4471/greenlet-3.5.4-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57aa201b351f7c7c75627c60d29e4d5b97a07d37efeb62b903466fca42c097d7", size = 625582, upload-time = "2026-07-22T12:29:00.814Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4a/f301f1d85c69a86b90b5d581a73e8927bba4e79450037e6e2cbca05eb4fd/greenlet-3.5.4-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9667862a2e38ad379f11b845daeda22c8989186def44f06962c9c4c05e556da7", size = 633429, upload-time = "2026-07-22T12:43:42.073Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/080f16cf870e929e592f55767f01d6c98d2ee83bfdc36c3b892f2d0459ab/greenlet-3.5.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3fe76c2cac86b4f7a1e92865ac0a54384deb05c92986287c1a7110d9bd53071", size = 624663, upload-time = "2026-07-22T11:51:08.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2e/26884072b0eb343a4d5fee903341bfe5171b32b7f14553886e2b6349135a/greenlet-3.5.4-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:ae53534b5dec0f4c2ec26f898f538dc8ea1ca3ef2927d597a9439e40a09da937", size = 428238, upload-time = "2026-07-22T12:39:49.973Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bb/8f3ca88370b817369008faeceeee85970adc16c92a70a3e5fe5fea495a57/greenlet-3.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e1a4a684b16c45ba324e60b32a4386a87722bcb815d2a149d2182f9b401ca72", size = 1585010, upload-time = "2026-07-22T12:25:02.539Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/45877154689709ebce9a0b83c2235e6ca0f31577889b02af308c8cc5f8fb/greenlet-3.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e849e6e139b9671adeac505f72fc05f4af7fd1921faef40295e214fc3b361b59", size = 1651283, upload-time = "2026-07-22T11:51:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/8711a75cb61d85246277c07ff6e1a6504621ba473d808c11ad225ffca43f/greenlet-3.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc418cf4c873357964d6624445ed09472e50def990c65dd4e76fc3ba8cd9cef6", size = 246434, upload-time = "2026-07-22T11:43:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/00/62/e290b3bce433da8f0324ac02da0b128d683482229f1a8b789fa47818a4cd/greenlet-3.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:c38c902a0986eba1f6e7ba1ab39ad5195926abde90f3fe080e08212db62176da", size = 244990, upload-time = "2026-07-22T11:39:22.626Z" }, + { url = "https://files.pythonhosted.org/packages/f3/04/81bd731d6d1e3a469d9a4c36f5eb069bcf0cbb2d5d342c9fec22245b91fc/greenlet-3.5.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3d66250e8b09f182ede05490998c818b5961f7a3640332d44c4927caec7bbfe4", size = 295909, upload-time = "2026-07-22T11:38:09.261Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dd/f5f22903a6ae70f5ea328ed0beaec92ad903f0e3b7d2845133b354abc4b8/greenlet-3.5.4-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c90e930c9c192e5b3ee9fb8bcd920ea3926155e2e3ded39fc697323addecee17", size = 612011, upload-time = "2026-07-22T12:26:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/8e/10/92a4a88d12b915d74ea5b6d288e4afefda4771647caa34442c156f7a454f/greenlet-3.5.4-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:791fdfeeb9c6e0c7b10fa151bf110d2a6974866f13dcb5b1c7efae698245893a", size = 624299, upload-time = "2026-07-22T12:29:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f9/03e26be3487c5238e81f2b84714959a86ea8515a869828cf41f4fc54b34e/greenlet-3.5.4-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b7c895310363f310361e0fe2072af85269d2a2a285cd04c0c59e79a5e3670dcf", size = 629603, upload-time = "2026-07-22T12:43:43.456Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/0b14bb9db2989f32cd9fe7f76afedea01ee8bee3f87c07e69f24adfe7e63/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f88193799d43dbf8c8a806d6405c9c52fe2af40bf75072a606357b33cc336c7f", size = 621541, upload-time = "2026-07-22T11:51:09.464Z" }, + { url = "https://files.pythonhosted.org/packages/57/6b/7c55ca72ef80d57c16c4a55210f82582622462dc4485799a30f4ec6f3372/greenlet-3.5.4-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:13b980043cb1b3134e81ea469da1250ddcc6bfe6d245bbaa59168d9cdc8f228f", size = 432554, upload-time = "2026-07-22T12:39:51.379Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/25e9a2d9eb6b2e8b7ca4e80a3a26cb887cce6c8e0a87c921164f11bc5574/greenlet-3.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b7a5f095767c4493afcd06067f2bb3b8716e3f3f9e92b99c88e7e99f885b3d4d", size = 1581444, upload-time = "2026-07-22T12:25:03.818Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/4c9bf2e2c408dcc0556edce69efa9f802e82223573c53240136a086821f1/greenlet-3.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42afdc1ab5f66da8c586c32af9224a74a706b4f0ea0dc3a4188a0860a09c65c9", size = 1645842, upload-time = "2026-07-22T11:51:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/b5/41/303ecb26a3a56122c0f4d4073ee078881847bd6b6f463ae0ec57ec20223b/greenlet-3.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:60149df8f462d1b230038e6590c23c3b4768bb5d6c022b3b6e82532b34b0b8a3", size = 247169, upload-time = "2026-07-22T11:38:19.893Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e3/ef56864b4c35fcb3eb3b41b869f6cc46f4cd3f5e2c68e74acde8ac433951/greenlet-3.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:77d6ce04fed0d9aeed42e0f37923cc43eba9b027bdd9c34546bb4ccd143d0fe0", size = 245565, upload-time = "2026-07-22T11:38:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/c0/9a/e51225dcd58713f16ccbdcc501a8da21098ea14515b7870f1f94459e5ff5/greenlet-3.5.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:24e61b88cb7e1b1d794b32a10cc346ac779681d6d74ff137a3e0a444d2bf1f02", size = 294831, upload-time = "2026-07-22T11:38:53.389Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/de50a50fadf979713ab18b46f22ad5ff5f2dcfc637a3ebdecf669801e1a5/greenlet-3.5.4-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:870d730fec833f5a06906a32596cc099b9161594642a92a520b7a88911c95356", size = 614619, upload-time = "2026-07-22T12:26:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/2aae27fea41205b8650294c301f042a2a4bb6155eea48c995b890a92f2c1/greenlet-3.5.4-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec5ff0d1878df6af3bf9b638a5a92a7d5693291de77c91bff10fa48519c604ef", size = 627021, upload-time = "2026-07-22T12:29:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/1b/80/fb4d4788bbc8e54761f1fc88533af9523a6e86299fa113d6e8a8503ed9fc/greenlet-3.5.4-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07bd44616608d873d06735b63ef1a88191d6ca57c8d291d6559c71bc14c0893c", size = 632845, upload-time = "2026-07-22T12:43:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/79fd826f9ccaae0b84e1b4ef68dabba5e105bb044ffcd448a0b782fcba9a/greenlet-3.5.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d84d993f6e575c950d91a23c1345d18fe1a4310d447bf630849d7809196b52f0", size = 624002, upload-time = "2026-07-22T11:51:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/6086fa578ebb72772722cdc4bcd628459814b42e0c2db1e3cbd6552b3271/greenlet-3.5.4-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:3529a8a933582ad19e224792cac7372489526576b75b4c124e8e4f29948f4861", size = 435053, upload-time = "2026-07-22T12:39:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/27319f97e731298513dcba1a2e91b63e9d8811d9de22130f960b129b1bf1/greenlet-3.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:58023945f421093de5e6fa108c0985a8659d43f49e0216da25099369a121bcbd", size = 1581533, upload-time = "2026-07-22T12:25:05.322Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6d/24240bf562e9786dd2799ee0a4a4dadb4ded22510f41b20245099159ac8c/greenlet-3.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bae2728e1897aa8df8cb1af38cd48b3a743aefe29372de7b8b7a9f532501e69f", size = 1645781, upload-time = "2026-07-22T11:51:14.805Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5a/442ab1a9ef7ca6bf7210e5397a95972206a91a31033a03c8900866a10039/greenlet-3.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:ca5726c0b08ca35ae873557266a78b2c3f3b2b7d7401aa5ff886c2045dd0111c", size = 247133, upload-time = "2026-07-22T11:39:20.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e6/9160210222386b1a378ff94db846b9508ca24a121cf684991561fdb69280/greenlet-3.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:7c1303791d603080cac6fc3b34df51c3b75b723739c282c8029e48a0d241672f", size = 245500, upload-time = "2026-07-22T11:40:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/6ab1d4f9cd548d15ab90da29947f2076100130bb179b0bde59f795a459e3/greenlet-3.5.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7e8afa5eac028f8140ceafe5ceec66e6aa127ddcb21452d2a564dcd2900b5f22", size = 295410, upload-time = "2026-07-22T11:40:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7a/422f63b4715cbc0b24385305407adf38b48f6bb68b3e6b04090e994d0f5a/greenlet-3.5.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73b37afe369021423ea53dd3123e04bffa7e93ac64429b9f50835b2e4fcae7cf", size = 661286, upload-time = "2026-07-22T12:26:43.8Z" }, + { url = "https://files.pythonhosted.org/packages/d0/31/5a1cac663bf5582190c5a714ef81364f03cde232227f39748f8ae4c11da5/greenlet-3.5.4-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ef964f56dfcb6f9bbef2a190d9126795eac408716aeae47b5e7c73c32aafca9", size = 673517, upload-time = "2026-07-22T12:29:04.815Z" }, + { url = "https://files.pythonhosted.org/packages/9c/bf/250c2921c7b585dde12f5239e313ca2dcbc464d161ecca36e4e6ef21762d/greenlet-3.5.4-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cef589bc65fae02d10bca2ac341191c5b33acc2967892ebf4fcbd10eabb7a74c", size = 677968, upload-time = "2026-07-22T12:43:46.788Z" }, + { url = "https://files.pythonhosted.org/packages/15/4a/2a82a1e3f8aaca020853ac8d12211280ca2b231aa08ea39f636f1060c319/greenlet-3.5.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c53ff01a5c53a40f2c16820ebc56d7c61a77f5fbe009dadd96292d5682f80f8", size = 670917, upload-time = "2026-07-22T11:51:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/18/40/10bfcf6513558d82f7b95dd728001c63bd388259fe27d3e30ae01f103430/greenlet-3.5.4-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:dfc41ae893d9ceaf22c824f2153a88b30651b20e8758c2cd9ac143f23640563c", size = 480643, upload-time = "2026-07-22T12:39:54.149Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/e379a152b17bfdfa95795af4049e37c0fd1b4d81f020d426db104ed07c77/greenlet-3.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecca4d80d55a01ad6b23b33262662956149fbb7b2c6be2910f1705921958cbf3", size = 1628478, upload-time = "2026-07-22T12:25:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/5e/43/bffdfa64f7317f954c5c1230b5dd5922676ce198689a68c1ac1ed4b1b1a5/greenlet-3.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ffbc533e0eaf8e80d8471411646ab88fe58f641d508c0b02b24494479f4d9ec", size = 1692021, upload-time = "2026-07-22T11:51:17.008Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/f799f9637e2c6e9b0b716015e339040598b058cf7654dfc0d67468b177ed/greenlet-3.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:305f69e6c4523d7f6979ed001cff4e5853c063e5da04880296603aa0227e544c", size = 248031, upload-time = "2026-07-22T11:40:11.007Z" }, + { url = "https://files.pythonhosted.org/packages/05/75/625bcdd74d5e6b2dca1ecba3c3ac77bcf8a026c21a649a46cef23e421f97/greenlet-3.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:f260930bbbbcf9caee661211235a5111c86dfe5832fdf6ae4570da1e0995320f", size = 246892, upload-time = "2026-07-22T11:40:27.357Z" }, + { url = "https://files.pythonhosted.org/packages/ec/69/35c62ed49c320cb4d98e14698ccca5467d3bfe683984172be9cb564d9ce3/greenlet-3.5.4-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:41ddab54e4b238f4a6c323f39b4e59e176affd5a94d461a9fb7583dac74240a3", size = 305571, upload-time = "2026-07-22T11:40:31.659Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/64d60216b3640dcb0b62d913dd9e0d80030c09115bb2e4ba70c95d10ca45/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3dabe3e2809013052c68bdf0b7fa5f5f2859c43a80803131ad61af9cabd7867", size = 672568, upload-time = "2026-07-22T12:26:45.298Z" }, + { url = "https://files.pythonhosted.org/packages/5c/de/ba3ab0a96292e53039530333b0d2ae18d9e508f3a325cd7bf15f8172944c/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:27d3f00718634d4520a3a150154ac5da36f257869d41321953375b90bfbbc72c", size = 680076, upload-time = "2026-07-22T12:29:06.125Z" }, + { url = "https://files.pythonhosted.org/packages/ae/db/24a10af12bf8e639cec46c38b9ce1a282543ba42ff4fb0b31a970f1ab603/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39169a11d87a6a263afda3e9a27d1df16d0f919d40a4837cc73986c9884c0dd8", size = 681690, upload-time = "2026-07-22T12:43:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/aeada79083c6f1c15f45d77a332f9c441af263ee298e3eb17522cd337d22/greenlet-3.5.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbd60b5763c6543c1827e48faaf14ea9bfbad245f52b1a4d76a2a2d8884c6c66", size = 676733, upload-time = "2026-07-22T11:51:16.027Z" }, + { url = "https://files.pythonhosted.org/packages/f4/60/44a2eca7b9fd71ae0fae7ff184da1cd3169d176652b97aa1cffcbb0ef961/greenlet-3.5.4-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:bd3d1145f603b2db19feb9078c2e6855eb7c67e15580c010ed815cee519b86fd", size = 510263, upload-time = "2026-07-22T12:39:55.678Z" }, + { url = "https://files.pythonhosted.org/packages/e9/10/2392fc3a98948652ef5fd1e7275c04f861dd13f74b78a2b4309f4ee4d090/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00f910f0e7b35416c63b23ad78b769aeccfc1775f712b43c4ee525624a2eef7", size = 1637327, upload-time = "2026-07-22T12:25:07.879Z" }, + { url = "https://files.pythonhosted.org/packages/55/c6/e7237a3dfa1f205ed0d9ea1e46d70bd2811b32d516266399fb59d28ab90a/greenlet-3.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:91c26423753b92caf41ab3f98fd547d7374d4d9fc2d85be041886c1579d9255e", size = 1697493, upload-time = "2026-07-22T11:51:19.214Z" }, + { url = "https://files.pythonhosted.org/packages/55/e3/4ba8154ba2a3d43729e499f72471b4b5c993f3826d3e24da81d5f06d6572/greenlet-3.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:ee032b91fd8ec29ec6c4cea2b8c561b178435134bd0752c7334b94e9c736c132", size = 251637, upload-time = "2026-07-22T11:40:37.44Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/e3f96dfc100261a29545ddc8270cafe58f9195b6651466910e820910de77/greenlet-3.5.4-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:178111881dd7a6c946471fda85485ec796e1043c2b939f694b096e2ecf986809", size = 296076, upload-time = "2026-07-22T11:39:38.364Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3d/da52d208e5c977bce8667e784729e584e38b5785f4c1ec0f4c836e9a1c42/greenlet-3.5.4-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d92df08dd65fede97fc37aad36c2e9dcda3b31c467f8e0c2c096456cb818e927", size = 666870, upload-time = "2026-07-22T12:26:46.691Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8a/7e6dee25cb8a8cf9b362c8e597cc269593378bd916f16c736c059a52e85a/greenlet-3.5.4-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99e8f8c4ebc4fd80aa26c1280ae9ad43a0976e786349703a181cf0bae60413e5", size = 677678, upload-time = "2026-07-22T12:29:07.508Z" }, + { url = "https://files.pythonhosted.org/packages/51/a7/dafc7415d430b0a43a16396eb49ecb3b62fd720877fb259cc4dcfaf5f31e/greenlet-3.5.4-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f17e362d78e37559e0506c5a7d066bdd45073c36a0127a543e8a0df27242ff3", size = 681428, upload-time = "2026-07-22T12:43:49.623Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/5a38699fa45de749e3857d93b8f07e4c20489e77c2d35d915a2e1c456606/greenlet-3.5.4-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:394de08dad5ffcb1f50c2159d93e398d9d2da3ed437645eaa54771fa720db9f0", size = 676067, upload-time = "2026-07-22T11:51:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d9/6298f3432de301d4718766cf934bd73c418c73f81fbb77247319364b0d96/greenlet-3.5.4-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:cd320d998cbaa032932830448e39abf3c6a12901295e386e8114db926e10cffb", size = 487446, upload-time = "2026-07-22T12:39:57.044Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ba/863116ab8ff1ca7a729e327800268939d182db47aa433db70e216e7d9194/greenlet-3.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:c883d61f2282d72c767a14936641b3efcbde9d82f1080712aaea0b1d3126cb88", size = 1633489, upload-time = "2026-07-22T12:25:09.605Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7466ced82818d6132462d7f26b3f83c66ea15d2b193a6c0088d558ed7d95/greenlet-3.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2a924f15d17957e252a810acefcb5942f5ca712298e8b6fcaed9a307d357522c", size = 1696584, upload-time = "2026-07-22T11:51:21.304Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4d/55b638489260065de9ffce606c8b5d04507bef705de4b212a0c3d6a1a0df/greenlet-3.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:ed17e5f3420360d5b459de8462efb52060399a5326a613d4cde31cef63ef95da", size = 248297, upload-time = "2026-07-22T11:42:04.055Z" }, + { url = "https://files.pythonhosted.org/packages/bb/08/9dd4ae635da93d41dc268bc34bd62a9d711ed8b8825c5d22ac910c7d6e6d/greenlet-3.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:f908898d6fa484ce4b6f447ce70ea99b52c503fee419e53cf74d60a16bc9e667", size = 247423, upload-time = "2026-07-22T11:44:00.764Z" }, + { url = "https://files.pythonhosted.org/packages/19/66/7c87ed9cdbf1d49c2c6cd1c7b9dd4d16c33b24235ca03972293a1876b30c/greenlet-3.5.4-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:1833637f17d5e7472548a48575c394fe39f1b1890d676d162d86593610f44d8c", size = 306487, upload-time = "2026-07-22T11:41:25.118Z" }, + { url = "https://files.pythonhosted.org/packages/5b/05/0a4201e7c0054866eefc05da234f236dd4c950d0fbf9ca0517141f01b269/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12cda9122e03341f1cb6b8207a19d7a9d375e52f1b4e9243918375f40fd7b4b9", size = 676479, upload-time = "2026-07-22T12:26:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/4b6b8c5a3aec70f0649cd89662d120fdd6421e2bdc8e3b15c3ab5ec568d8/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d83ae0e32d14957ab7170785a20f582635c8474deab1bfbb552b17e769a6ce25", size = 684321, upload-time = "2026-07-22T12:29:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/88/15/0b167aeea95285b0e654ddce651922f666c089363c2ec528ca8b9a9ba74f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:123aa379c962ed5fe90a880327e0c3066124ac64ec99e12a238be9fd8eb3db3d", size = 685995, upload-time = "2026-07-22T12:43:50.993Z" }, + { url = "https://files.pythonhosted.org/packages/24/c9/b49c31c9a972eee91e260445770e922244a7efc542697f51a012ec046d0f/greenlet-3.5.4-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f1467de1bb767f75db0aa34c195e3a496d8d1278c796e70c24ce205d3e99cde", size = 681293, upload-time = "2026-07-22T11:51:20.43Z" }, + { url = "https://files.pythonhosted.org/packages/de/90/c023ec337f32ff505be7db759c80d98f0532bb94d0c6fa13645efe9bee2e/greenlet-3.5.4-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:adf2244d7f69409925a8f22ed22cc5f93cdfe5c9dc87ff3476be2c2aaae61a05", size = 516928, upload-time = "2026-07-22T12:39:58.359Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/7f2d4653770500eee667866016d42d7a68e3d3462f80df6b8e3fcd48a0eb/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0fa53040b78b578120eecdc0265e3f1051487cc425d11a2b7c761daadf4feaa8", size = 1642474, upload-time = "2026-07-22T12:25:10.819Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d8/8cba31036a4caae448087ba5d150660ab03b4a0f54d9150f6495a3be7262/greenlet-3.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:60e0bc961d367df506660e9ac0177a76bc6d81305300704b0977d1634f76efe2", size = 1701012, upload-time = "2026-07-22T11:51:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cd/3f77a4cce3bae631b08eb52f53a82a976669600337e21dfdba811cb50267/greenlet-3.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:f680e549edb3eaf21eea4e7fe101e15ec180c74b7879ab46adc080f22d4015d2", size = 251977, upload-time = "2026-07-22T11:41:38.125Z" }, + { url = "https://files.pythonhosted.org/packages/93/e8/65e8707d00fe2a49bf12f609a9b2b39ba6dd23c2810eacad877c4fc94bfe/greenlet-3.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:08fc36de8442d5c3e95b044550dbea9bf144d31ec0cc58e36fb241cb6ef6a994", size = 250538, upload-time = "2026-07-22T11:40:17.985Z" }, +] + +[[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 = "h5netcdf" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/03/92d6cc02c0055158167255980461155d6e17f1c4143c03f8bcc18d3e3f3a/h5netcdf-1.8.1.tar.gz", hash = "sha256:9b396a4cc346050fc1a4df8523bc1853681ec3544e0449027ae397cb953c7a16", size = 78679, upload-time = "2026-01-23T07:35:31.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl", hash = "sha256:a76ed7cfc9b8a8908ea7057c4e57e27307acff1049b7f5ed52db6c2247636879", size = 62915, upload-time = "2026-01-23T07:35:30.195Z" }, +] + +[[package]] +name = "h5py" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/6b/231413e58a787a89b316bb0d1777da3c62257e4797e09afd8d17ad3549dc/h5py-3.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e06f864bedb2c8e7c1358e6c73af48519e317457c444d6f3d332bb4e8fa6d7d9", size = 3724137, upload-time = "2026-03-06T13:47:35.242Z" }, + { url = "https://files.pythonhosted.org/packages/74/f9/557ce3aad0fe8471fb5279bab0fc56ea473858a022c4ce8a0b8f303d64e9/h5py-3.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec86d4fffd87a0f4cb3d5796ceb5a50123a2a6d99b43e616e5504e66a953eca3", size = 3090112, upload-time = "2026-03-06T13:47:37.634Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/e15b3d0dc8a18e56409a839e6468d6fb589bc5207c917399c2e0706eeb44/h5py-3.16.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:86385ea895508220b8a7e45efa428aeafaa586bd737c7af9ee04661d8d84a10d", size = 4844847, upload-time = "2026-03-06T13:47:39.811Z" }, + { url = "https://files.pythonhosted.org/packages/cb/92/a8851d936547efe30cc0ce5245feac01f3ec6171f7899bc3f775c72030b3/h5py-3.16.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8975273c2c5921c25700193b408e28d6bdd0111c37468b2d4e25dcec4cd1d84d", size = 5065352, upload-time = "2026-03-06T13:47:41.489Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ae/f2adc5d0ca9626db3277a3d87516e124cbc5d0eea0bd79bc085702d04f2c/h5py-3.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1677ad48b703f44efc9ea0c3ab284527f81bc4f318386aaaebc5fede6bbae56f", size = 4839173, upload-time = "2026-03-06T13:47:43.586Z" }, + { url = "https://files.pythonhosted.org/packages/64/0b/e0c8c69da1d8838da023a50cd3080eae5d475691f7636b35eff20bb6ef20/h5py-3.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c4dd4cf5f0a4e36083f73172f6cfc25a5710789269547f132a20975bfe2434c", size = 5076216, upload-time = "2026-03-06T13:47:45.315Z" }, + { url = "https://files.pythonhosted.org/packages/66/35/d88fd6718832133c885004c61ceeeb24dbd6397ef877dbed6b3a64d6a286/h5py-3.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:bdef06507725b455fccba9c16529121a5e1fbf56aa375f7d9713d9e8ff42454d", size = 3183639, upload-time = "2026-03-06T13:47:47.041Z" }, + { url = "https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" }, + { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" }, + { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" }, + { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/39/67be8d71f900d9a55761b6022821d6679fb56c64f1b6063d5af2c2606727/hf_xet-1.5.2.tar.gz", hash = "sha256:73044bd31bae33c984af832d19c752a0dffb67518fee9ddbd91d616e1101cf47", size = 903674, upload-time = "2026-07-16T17:29:56.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/be/525eabac5d1736b679c39e342ecd4292534012546a2d18f0043c8e3b6021/hf_xet-1.5.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4a5ecb9cda8512ba2aa8ee5d37c87a1422992165892d653098c7b90247481c3b", size = 4064284, upload-time = "2026-07-16T17:29:29.907Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3f/699749dd78442480eda4e4fca494284b0e3542e4063cc37654d5fdc929e6/hf_xet-1.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8764488197c1d7b1378c8438c18d2eea902e150dbca0b0f0d2d32603fb9b5576", size = 3828537, upload-time = "2026-07-16T17:29:31.549Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/2658ac0a5b9f4664ca27ce31bd015044fe9dea50ed455fb5197aba819c11/hf_xet-1.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d7446f72abbf7e01ca5ff131786bc2e74a56393462c17a6bf1e303fbab81db4", size = 4417133, upload-time = "2026-07-16T17:29:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/d9/58/8343f3cb63c8fa058d576136df3871550f7d5214a8f048a7ea2eab6ac906/hf_xet-1.5.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:580e59e29bf37aece1f2b68537de1e3fb04f43a23d910dcf6f128280b5bfbba4", size = 4212613, upload-time = "2026-07-16T17:29:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/0c/33/a968f4e4535037b36941ec00714625fb60e026302407e7e26ca9f3e65f4e/hf_xet-1.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bee28c619622d36968056532fd49cf2b35ca75099b1d616c31a618a893491380", size = 4412710, upload-time = "2026-07-16T17:29:36.646Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/9e33981173dbaf194ba0015202b02d467b624d44d4eba89e1bf06c0d2995/hf_xet-1.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e396ab0faf6298199ad7a95305c3ca8498cb825978a6485be6d00587ee4ec577", size = 4628455, upload-time = "2026-07-16T17:29:38.352Z" }, + { url = "https://files.pythonhosted.org/packages/e9/4b/cc682832de4264a03880a2d1b5ec3e1fab3bf307f508817250baafdb9996/hf_xet-1.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fd3add255549e8ef58fa35b2e42dc016961c050600444e7d77d030ba6b57120e", size = 3979044, upload-time = "2026-07-16T17:29:40.329Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/b2cdf2a0fb39a08af3222b96092a36bd3b40c54123eef07de4422e870971/hf_xet-1.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d6f9c58549407b84b9a5383afd68db0acc42345326a3159990b36a5ca8a20e4e", size = 3808037, upload-time = "2026-07-16T17:29:42.357Z" }, + { url = "https://files.pythonhosted.org/packages/de/ba/2b70603c7552db82baeb2623e2336898304a17328845151be4fe1f48d420/hf_xet-1.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f922b8f5fb84f1dd3d7ab7a1316354a1bca9b1c73ecfc19c76e51a2a49d29799", size = 4033760, upload-time = "2026-07-16T17:29:43.884Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/b097a86a1e4a6098f3a79382643ab09d5733d87ccc864877ad1e12b49b70/hf_xet-1.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:045f84440c55cdeb659cf1a1dd48c77bcd0d2e93632e2fea8f2c3bdee79f38ed", size = 3841438, upload-time = "2026-07-16T17:29:45.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/35/db860aa3a0780660324a506ad4b3d322ddc6ecbba4b9340aed0942cbf21c/hf_xet-1.5.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db78c39c83d6279daddc98e2238f373ab8980685556d42472b4ec51abcf03e8c", size = 4428006, upload-time = "2026-07-16T17:29:46.996Z" }, + { url = "https://files.pythonhosted.org/packages/af/6b/832dd980af4b0c3ae0660e309285f2ffcdff2faa38129390dbb47aa4a3f9/hf_xet-1.5.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7db73c810500c54c6760be8c39d4b2e476974de85424c50063efc22fdda13025", size = 4221099, upload-time = "2026-07-16T17:29:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/9e/05/ae50f0d34e3254e6c3e208beb2519f6b8673016fc4b3643badaf6450d186/hf_xet-1.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6395cfe3c9cbead4f16b31808b0e67eac428b66c656f856e99636adaddea878f", size = 4420766, upload-time = "2026-07-16T17:29:50.092Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/c050bc2743a2bcd68928bfee157b08681667a164a24ec95fbfcfcd717e08/hf_xet-1.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cde8cd167126bb6109b2ceb19b844433a4988643e8f3e01dd9dd0e4a34535097", size = 4636716, upload-time = "2026-07-16T17:29:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/68b01c5c2edb56ac9a67b3d076ffddcb90867abaee923923eb34e7a14e76/hf_xet-1.5.2-cp38-abi3-win_amd64.whl", hash = "sha256:ecf63d1cb69a9a7319910f8f83fcf9b46e7a32dfcf4b8f8eeddb55f647306e65", size = 3988373, upload-time = "2026-07-16T17:29:53.395Z" }, + { url = "https://files.pythonhosted.org/packages/39/c6/988383e9dc17294d536fcbcd6fd16eed882e411ad16c954984a53e47b09c/hf_xet-1.5.2-cp38-abi3-win_arm64.whl", hash = "sha256:1da28519496eb7c8094c11e4d25509b4a468457a0302d58136099db2fd9a671d", size = 3816957, upload-time = "2026-07-16T17:29:54.991Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "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/40/b9/be66eb0decd730d89b9c94f930e4b8d87787b05724bb84af98bfd825f72c/httptools-0.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826", size = 208805, upload-time = "2026-05-25T22:16:50.434Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f7/b4d41eaae2869d31356bc4bbf546f44fae83ff298af0a043ca0625b06773/httptools-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77", size = 113527, upload-time = "2026-05-25T22:16:51.672Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e4/77487e14fc7be47180fd0eb4267c7486d0cc59b74031839a3daf8650136b/httptools-0.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4", size = 450035, upload-time = "2026-05-25T22:16:53.313Z" }, + { url = "https://files.pythonhosted.org/packages/da/72/5a8f787e323f56fbd86c32a4be92a86776e4cfe8b4317db999f452028362/httptools-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb", size = 451101, upload-time = "2026-05-25T22:16:54.696Z" }, + { url = "https://files.pythonhosted.org/packages/ed/41/b44a25560955197674b6744cb903664300e239235a5eaa69df0890d87054/httptools-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813", size = 436140, upload-time = "2026-05-25T22:16:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/74/b0/054aac84c03d7e097bf4c605fb7e74eec3d65c0276adf64ee97f3a103ff5/httptools-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba", size = 437041, upload-time = "2026-05-25T22:16:57.716Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e8/86b85bbc0ac7892232f1a99ab96a9aa71936984fa06adfc0afc83ca7789e/httptools-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557", size = 90454, upload-time = "2026-05-25T22:16:58.871Z" }, + { 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 = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huey" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/84/7c4d1a19b1904020c527ce8fcb9c98d8f29da3ae2d6116edb9fbef51c18a/huey-3.3.0.tar.gz", hash = "sha256:e0c2a1542e6c3acb894821cde895cf9dbf72e42deb25f55b2d44096aa30f8f24", size = 611969, upload-time = "2026-07-22T15:45:38.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/5f/2d94fdf94829644d7782b1c8c97e6d4c00cb048069d8fa4b71cf366e6f27/huey-3.3.0-py3-none-any.whl", hash = "sha256:a7b1581aaae5f70540faafcf3306d74fa138ec7bad520f81150bb70b224eaddf", size = 121320, upload-time = "2026-07-22T15:45:37.249Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, +] + +[[package]] +name = "hyperopt" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/40/7701d2022ca9aa07083d2b6ac4fe57142d084a6b92c50cc924d430f1b2b8/hyperopt-0.3.0.tar.gz", hash = "sha256:d79a77522fefec13a258b97a0ccbdf40832b3270de4db08278bb07b9b934eb9a", size = 1193904, upload-time = "2026-07-24T13:39:47.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/e9/a570402c6df9df203b114a27e90cf5fa2a1a18ac6567c058bea7a6c89ae4/hyperopt-0.3.0-py3-none-any.whl", hash = "sha256:f2533e4363ebc0c7e9e5a5322243ece23bf62956e6e236f489080298f558404c", size = 973547, upload-time = "2026-07-24T13:39:45.563Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "ijson" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/06/b31f040a8764336a11152e474a7abcb3782fedb0d1cdf78f442b82878c56/ijson-3.5.1.tar.gz", hash = "sha256:af40bd1a85f55db0b8b30715c858761306bd92d5590148636f75c3309e6e76bd", size = 69913, upload-time = "2026-07-06T17:37:42.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/b8/6401c0e2f99aeff22fc740a1b1c2328269a81050c0c178462d0452e27c7e/ijson-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8b4ed62287feee41b90b55ae2800ef56d6bdfd2fbfa02b4fd0634cd4524bc995", size = 89054, upload-time = "2026-07-06T17:36:03.274Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ad/8d9e1f076560efcc6727b06f3276f30bb811961332d83567de70c179e0e8/ijson-3.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9708c0a3d1f86056049de631933aef8ec57f2008d4cb55ce241790c7ed557428", size = 60674, upload-time = "2026-07-06T17:36:04.326Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e7/8f001e823846c270e0e9c3526ea99dc3b1ba51b9501e060d8337830d6c76/ijson-3.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:904e8cf9ca69f5de5b6bb405a4a075ce3da3413ad50c11f6813f1201e14a8e45", size = 60738, upload-time = "2026-07-06T17:36:05.283Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c023067cb5ba4cc455a92110a021863fbe3dc3ffcca34ef95aea9290b8f1/ijson-3.5.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8cb5db5bc122da64efb24ce358752d5e097ab41d224ce2992536a0f9073fe4fd", size = 126651, upload-time = "2026-07-06T17:36:06.211Z" }, + { url = "https://files.pythonhosted.org/packages/a6/93/7c2207377b40bc1227c8fe1811e080f3b73cd4a9486af9c1166486c3156c/ijson-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cae04eff4006fc36bf0b030b38e2646a97092d87d933d20cfe7262e26ed32321", size = 133200, upload-time = "2026-07-06T17:36:07.239Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ea/e4d3f64822fb29d54970909e1e2784daa17f75fe3c6c27544fe92e247aad/ijson-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70542d4542f079c394e525559188d69e3ccfbfd9bab899acd0bf1dbc7323ddd5", size = 130361, upload-time = "2026-07-06T17:36:08.332Z" }, + { url = "https://files.pythonhosted.org/packages/03/77/a61b6b68868a7368a0e4335975c5352e6c354d05eb73dbef19e796b3eaab/ijson-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1321495807dcdaca002cb45f24033208ce1d9f5ffc0c5a5584c5f466d0dcbbd5", size = 133618, upload-time = "2026-07-06T17:36:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0c/05bde03ef651ae2e1033f136c56f7f5565e9f53e7ff91ca83bfd581cbafa/ijson-3.5.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9fac9284d62c4317d541274e15a6a6ab6f6d22561579f6570967e3a6eaafaebc", size = 128554, upload-time = "2026-07-06T17:36:10.464Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/29bb5561c60e1f9d58d4fbef686e35b9440d9b56f9254c1c70b807c8f649/ijson-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1be3a586c8821ecab9ea8b256f39305c8a0cc33222fe393bcc1fb9221470732b", size = 131233, upload-time = "2026-07-06T17:36:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/69/f7/b0176baac5129b79aa366161d5f524ead91b901f16a5020e495c3f83bcc5/ijson-3.5.1-cp310-cp310-win32.whl", hash = "sha256:3ab6378d9c19f01f206f27f762837ad3979330cabd7864e1b17934c03de6056c", size = 52221, upload-time = "2026-07-06T17:36:12.806Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/a707b5830722e9f7af347945f9ee0f360d38922366bc1400c6177154eb9c/ijson-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:0663f718c6123899c6bfd9c449ec195cd8c67666b7ea2c7b36fa0cc0dcb13e17", size = 54641, upload-time = "2026-07-06T17:36:13.724Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/834e7a4ec7e1019b596daf8d74f697aa1d3e38a17a9c31af6081c070557b/ijson-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:0a682954b60fcd0c23d504df6fb1ebde051305e41c9b350f39a3b8bfb168def7", size = 53954, upload-time = "2026-07-06T17:36:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/97/d3/16d1595d3ef4743fc55129211bc52f52d59c582d0b7be045d8c04be0ae0c/ijson-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2aa9d0cf21d4de89fb633e5ec27e9ad02c3f9a4ffa3940d120b23b8aed3acffc", size = 89069, upload-time = "2026-07-06T17:36:15.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/a5/ddba126e2d46cf3b86ad762aeb5e0a02ce0ebc6e4529fe7d06eecb217844/ijson-3.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:05eba5268a38809ba1c3dbfa44ea67336e2c353fc11768acc9c6442fe0ccac50", size = 60697, upload-time = "2026-07-06T17:36:16.66Z" }, + { url = "https://files.pythonhosted.org/packages/dc/74/444d8d00a4506a79fc5544614106fa48d5f6f7049511148d8b6cddb8e9d7/ijson-3.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:40ddd236c80a667dd6a1f6b625d18ddac68b8719ff795761b7542f2e1f78e4a4", size = 60747, upload-time = "2026-07-06T17:36:17.927Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b1/bc07831e646aebcc91a7bad9c5a0bf7c3f3395f0b10599e021667a3777f1/ijson-3.5.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e6cf9e49902f28af7a2e2f8b35c201195c0f0d5c170a5786e0c0a1b8492a4e37", size = 132095, upload-time = "2026-07-06T17:36:19.022Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/b4547461d75db40744616e40c0a06cf2f46a14e60742f6d12510f4612985/ijson-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ee1e6d59c800aa819952f6cb5ff08707ecd576b29cc9c3d00e33c2b371a92ce", size = 138790, upload-time = "2026-07-06T17:36:20.22Z" }, + { url = "https://files.pythonhosted.org/packages/a7/30/7ecba8377509eaea2666db5b39a1a99e23f5e3e1e7ee371ec366cbfc4f7c/ijson-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:affb85eb75fa03a21d1f790bbf26a0e66e5701672062a30dc5c3c6a29c5c0a63", size = 135233, upload-time = "2026-07-06T17:36:21.252Z" }, + { url = "https://files.pythonhosted.org/packages/38/36/0679010904b24398336b3099b09ccb1daa41c534e7cb0931e89d5fcdbee4/ijson-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3060b141ef758be3742315d44476109460c265b88247e3a4e479949f8b134eac", size = 138832, upload-time = "2026-07-06T17:36:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/b0/90/a40f971e78191e423c7b3a23756f37c3a51c27aadd7769b3fb1816e0044d/ijson-3.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ffba9bce60be21b496afc67a05ab8e3f431f87f0282fd6ce3c62004c951a1428", size = 133313, upload-time = "2026-07-06T17:36:23.405Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d7/b012c347d3ab011c0c4f7988dc6e85b83eaab59df1aec089f5db0e7b29c5/ijson-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:170cc4c209f57decc9b7ee5fd340f2a1602d54020fa222846482ff1c99e88fdc", size = 135706, upload-time = "2026-07-06T17:36:24.464Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/3eacb96124e78271f4e648c6ce36f9ce15ce2cef2afb6f8dc6e213e43979/ijson-3.5.1-cp311-cp311-win32.whl", hash = "sha256:6d581a071dae8dbee61f8d962e892787707bad6e641e2f6fb30dd89d3e896939", size = 52221, upload-time = "2026-07-06T17:36:25.517Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1a/19eff8576da0b46fa4a5c8751536ea27ab34c44b2609b2bcded9d7808d42/ijson-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:1356bca96d015948b601b013defb2d5631e4330e8f5880e4d7c933d472a90c34", size = 54641, upload-time = "2026-07-06T17:36:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/c7/80/86b28f28ebf190fffd4f46790e065311e2758b55d8e6bbd33d92e9a49448/ijson-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2b83b24be73f0c7a301807a4c3081939524421c7ae1556eb6eac7cff50ddfa7", size = 53954, upload-time = "2026-07-06T17:36:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6e/f3ded1ebb85ccc89a30f7b10a0076f30db70ae1d1e0b6423ff93c57b7539/ijson-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee60c7741012671867678eae71c51872cac938b76f3d4ca40a778e6c361774d2", size = 88643, upload-time = "2026-07-06T17:36:28.529Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f2/18f14a1d79ef4898e746b4f50dcdbe60abab317cc2bd8390f043b9553c4e/ijson-3.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:11c1d7d36a13054b5872ecd5d745dc4009d9abdbcba2312de69e66c2f92a46d2", size = 60611, upload-time = "2026-07-06T17:36:29.597Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/6e3e591324fd4c7a7a9e1bc23548bacbd84c0d91766b71f09f13e945e7e9/ijson-3.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9517efbe6604bce16f3e50d49b0cd1bdc58917f98cf2eab026599c5c0422991", size = 60447, upload-time = "2026-07-06T17:36:30.747Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a5/9af7be670381ddac26dd55107ed0110b50f5161673b053311db67f510dcc/ijson-3.5.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea4fd7bec203a600b1cc88a492dfe6b75ce4b1b87488a66adcd5406022213f64", size = 139092, upload-time = "2026-07-06T17:36:31.749Z" }, + { url = "https://files.pythonhosted.org/packages/41/fb/f9c1664d75467453e6bd4e5f9cd2211b730b09e049445ab64cbac68cc6a3/ijson-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350caea815e53151994b597abc80cf669454276b5ac6aadcec69ef6d48f7e90b", size = 149921, upload-time = "2026-07-06T17:36:32.912Z" }, + { url = "https://files.pythonhosted.org/packages/43/80/d20b1c49c4aa7cc6644131e2e57192b45346ef4816566ed1cd9fd05bae38/ijson-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4fcebfe1685bb7ba06a8255a5d428ea6b4b895d7acf979cb637d8bbc9db2f47", size = 149848, upload-time = "2026-07-06T17:36:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fc/5baa710869f5ab939e6233583ced1546889b55c35f35b844c518ac10abc3/ijson-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d78f362f51c8691798758a9e6ac3c9d385ee1228cb82987c91562a2fae235cd3", size = 150810, upload-time = "2026-07-06T17:36:35.19Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/a12b3d987a5c1677b04557c6f9b9feb7e04b7d4171e9a344856cb9136e9b/ijson-3.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0b184180d45f85fd4479659582749b109e49f4a29c21ac700ccc9c2280fe015e", size = 142989, upload-time = "2026-07-06T17:36:36.23Z" }, + { url = "https://files.pythonhosted.org/packages/ed/63/1026c535671fc334fc85aeb78f0945c825e7a338575edc753c0f455459ae/ijson-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e353891d33a2e6aa5caf72c2a5fbadd7a46f5f9b32dcfd0c84113b2444c255b8", size = 151702, upload-time = "2026-07-06T17:36:37.296Z" }, + { url = "https://files.pythonhosted.org/packages/cb/af/b58aa3a2bf4d31c388ea78b49826605f60932891ce97e404d196766b4ea3/ijson-3.5.1-cp312-cp312-win32.whl", hash = "sha256:936f28671f018f8ac4d3f003ae9fa01d0467ab4ef4cfd0c97f23beda485b61c6", size = 52613, upload-time = "2026-07-06T17:36:38.345Z" }, + { url = "https://files.pythonhosted.org/packages/04/66/ce70a92949c2a753dad91fdd5761dc14f3a44517e80cfc3c26612982ed61/ijson-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:322c783f3ee0c6b383bbd4db88370b10172168808cc2a0bf811f1253f7435602", size = 54729, upload-time = "2026-07-06T17:36:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/e17784240c9cf1d58de2f2853ebaf9cc54f6bce117a1f12a6150bbb4a5aa/ijson-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:e2ac204b59f09e38e16d277f906240e9fd38780e42076599419265af183dc4b4", size = 53714, upload-time = "2026-07-06T17:36:40.308Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c0/5384ccf4fc497ae3dc79a5a28561b05518b503ade29daf3898168d640406/ijson-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3c0556d628443d3e871f414855313b2ae6cd9faa0104de3316bd8db03aab1589", size = 88652, upload-time = "2026-07-06T17:36:41.278Z" }, + { url = "https://files.pythonhosted.org/packages/8e/42/58769b8b6d614adb15c2c938c77bcdbfadfba8b1d21a98b5b09cb8961adc/ijson-3.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12aa7fcf46f0fdc8e9e7cf37541e1dc20ac3f9243a23f4d346ab5395f72b0fe2", size = 60607, upload-time = "2026-07-06T17:36:42.697Z" }, + { url = "https://files.pythonhosted.org/packages/db/4a/8322c2824c24184880587bbca45531127a21a4b3bfc897f13427fea02424/ijson-3.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a96066d8c12a18ce2fa90579f2bbf991377cb71725874932e4a5d855226c162a", size = 60447, upload-time = "2026-07-06T17:36:43.791Z" }, + { url = "https://files.pythonhosted.org/packages/f4/43/7bdca8f733c45ce97f61a64fadd3e51d255c4c9b467345cbf71ccc7bb368/ijson-3.5.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a19413a092d458a57aaa574fec08e265851d3b5c6e018377f426cd5e70b91280", size = 138889, upload-time = "2026-07-06T17:36:45.081Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/e8a2e63700ab1d63aaf3fa38c454f8178eaa5b80a6d7c019d1d61b490a6c/ijson-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65974568748678165d7e90e3e7ce2f7c233cfe4de6c37fbb0760941c97e14632", size = 149933, upload-time = "2026-07-06T17:36:46.312Z" }, + { url = "https://files.pythonhosted.org/packages/d9/56/640a4d980f7f2c11e399a7fd5ccb9e3d3c9e1dec3a1d5a10024570697c25/ijson-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bad5d55c99c89de8cd0a4cded51f86427ba3353c4dccca37ec2e32e06f26b437", size = 149857, upload-time = "2026-07-06T17:36:47.309Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a1/c953e22c83992b69ae538a83b3678d28768f1a48042fc7794733423a5ce7/ijson-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1a38d503ce343952e88edfd9a27296a4ec96af7073a9db58b3df6233367f75fc", size = 151141, upload-time = "2026-07-06T17:36:48.405Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ab/8fe5b7269b140e6e5f8837a33ce980fd9b67c70d0f8114289ed1cea4dace/ijson-3.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2f41982c73896acab4a2a14faa14e152e444bd69f37c3139204429fd3fe65a10", size = 143112, upload-time = "2026-07-06T17:36:50.353Z" }, + { url = "https://files.pythonhosted.org/packages/78/f3/23d1284edcde50ba337ddfba5b5d59f8273084d98b28af94715e73dd2b64/ijson-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3321fede2b638d400de0036889a3a25c3bb689feb8df45e70a393346aad6194f", size = 152184, upload-time = "2026-07-06T17:36:51.536Z" }, + { url = "https://files.pythonhosted.org/packages/82/4e/df61be89dd295e4da722ec96ba03b1765bcb2becdaaaede9c96a7d2365b6/ijson-3.5.1-cp313-cp313-win32.whl", hash = "sha256:af6ddbd10ac9bce87a835f2de3ec61455ec435c54e7e0ba7b17c31c66de6f164", size = 52607, upload-time = "2026-07-06T17:36:52.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/03e5dbd3ef7e0cee06fbef0f87b91d7ce1c07fae9b5a1b0ca8b895de62c4/ijson-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:1de3de278b0ffb40338374ad2a730e1c56f933e0706b1815ebeb07b82239b1a3", size = 54730, upload-time = "2026-07-06T17:36:53.526Z" }, + { url = "https://files.pythonhosted.org/packages/38/30/4f37076c88a96a1a5e44df38b59fade4f59eaef87ef8b5162d55b2d426d5/ijson-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:c8a36a19b92cb7172c6448ab94f446033cfa3129dc4894aebe205f96b3fabf42", size = 53719, upload-time = "2026-07-06T17:36:54.592Z" }, + { url = "https://files.pythonhosted.org/packages/f9/17/54f9180c0da9a9e96e5b3791bc74093f029a2344678b4da218c2699465bf/ijson-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21e1a250b254edba2f0dd7272a4c56f0a879aabe328d9e306dd1fc115f560e74", size = 89223, upload-time = "2026-07-06T17:36:55.534Z" }, + { url = "https://files.pythonhosted.org/packages/09/70/0ee0d2627c534174455a745ca25284797e71b0d6e2b2a1b31cc914e7b462/ijson-3.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e01f95433725e2df62d682ff88e4a57bb694385ff2362bc364adec961167ae04", size = 60831, upload-time = "2026-07-06T17:36:56.554Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e6/56f64ba7a3e7a25d9a9fbbeb4c30597d6b76c1094cc2041d11a3224b562c/ijson-3.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:539e8d6cca079bcbb68c390e55148f908e0a943a34f7dd321248637c6272adca", size = 60752, upload-time = "2026-07-06T17:36:57.826Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2b/5a55db881f1b043cd6d5716578937a60ac16348be1a3afbf846b21cf4b44/ijson-3.5.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:32f64051be2f990d8ae7b614b5abdf4a7bead510ce3666568d7403c6c46ce4d8", size = 140783, upload-time = "2026-07-06T17:36:58.984Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/f7783cc18672dc31544141139efd187fb34795d24e573fed6abea6b776c7/ijson-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd0dfc5a788d0b0c2f1eab258b9dabdeefc631ca8ef87644a999f633b0b2555a", size = 149976, upload-time = "2026-07-06T17:37:00.235Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/4182dd63b6b70eae4f5208c53558a050895a40734dff283463033c153742/ijson-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42bfda7858d99ee9777ec28cb6d347928249eefeb577f9b0a67503c18f7ebb6a", size = 149317, upload-time = "2026-07-06T17:37:01.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/b1/a675e4a9b428a0ef556e7d718bf0e6885e3e5543042248a1a7030899a3d4/ijson-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4b9a28e9719d1aebebe93ad8dc2ba87f4e2d9035043b196c1c07ef8530b44cc", size = 150555, upload-time = "2026-07-06T17:37:02.676Z" }, + { url = "https://files.pythonhosted.org/packages/b5/69/52686f56b44af63a93c3dc3f5bcfa07f87427d9aea4d2cbe3e1c94188c74/ijson-3.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9a0b25c750a6bde14a0b31f1dcbfc86368e50767e3eaa73bb138e54128055edd", size = 144485, upload-time = "2026-07-06T17:37:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/f0/46/10554e817dde56300a8414e52c0f5a44a29f3440327cd6d829ece57759b3/ijson-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bd756f7b22df745ac14b7bc2ab9ed7c190a222e4c8e1bef26ef1162af8e54d0f", size = 151470, upload-time = "2026-07-06T17:37:04.901Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/f37cbb110b48abdb623d169d0e196f2f6e064e2c20fa789ecde6e69b0440/ijson-3.5.1-cp314-cp314-win32.whl", hash = "sha256:e035cdfb2a1446b13881f0dfc0eecd1541cbb17a27a938ded2160ae6ce25051b", size = 53219, upload-time = "2026-07-06T17:37:06.254Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/792df8f001c246c8ff28f860de81d35ea0d797c0d3276c22a2af83089656/ijson-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:eeb2fb2daa5dd30326f93db465d0855b34aa6b1f52a7c0ff94522aec5ad57dfb", size = 55485, upload-time = "2026-07-06T17:37:07.242Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3c/db3ccc22c09ed4738787e8d82fff76101aa81ec8de7eaf6572e065e012d3/ijson-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:a96ab35d7ce2129dfde49c4c807596443410e260d7f7a4ca8fe4d0035553b589", size = 54390, upload-time = "2026-07-06T17:37:08.497Z" }, + { url = "https://files.pythonhosted.org/packages/26/59/eefa5d9488250c03f24152576804205ae40e29cac0dc65cbbc5f3d422008/ijson-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:77b68e91f95fb16ac2e7819903cd545db6cffa308c28833cc34911e6b21e91dd", size = 93177, upload-time = "2026-07-06T17:37:09.71Z" }, + { url = "https://files.pythonhosted.org/packages/88/db/6329eb7bb9f1906c1906fc10e7074b8f08bf39b7d50baa58f1b597d48898/ijson-3.5.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:94a95065b1ac67602af0cec852b07505abc37b77e3774d1c801d935d05e48f82", size = 62891, upload-time = "2026-07-06T17:37:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d0/b3beddb96eef0b20bb9902c36e4de30f145be06d7e5e1d780e1a1689d0ce/ijson-3.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b70b5da6b0571da8f601a437c4fba2d35bc27739637d85f3acdc8f88916ce68e", size = 62575, upload-time = "2026-07-06T17:37:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/95f3a7c27d25bb917954ef0c8e86d0e60f585b9db675cbd05d355f54cce8/ijson-3.5.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0ade373dd765b057b1dec05d7711bfeb5a36f1e825259466d9f545cfd8ef3ba3", size = 200568, upload-time = "2026-07-06T17:37:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/77/61/c94ee4ea1f22318aab9a49b35d0ce8ac87dd24d508ea4c77dcbde362ba5e/ijson-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:882bc0bdd25d41eae90a15695cd50707edde0978b8b72a2532e30442dd8fd04c", size = 217956, upload-time = "2026-07-06T17:37:14.041Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/43e8d225aea5ee00eef7998c8ce41f344f7ba451329dfa9e92f4700813af/ijson-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451901c36e12fa87cbb1cafe661bd25c08c6bd7900cc738279614f71cea07048", size = 208403, upload-time = "2026-07-06T17:37:15.201Z" }, + { url = "https://files.pythonhosted.org/packages/cf/6f/375f67fad76677aca9bc0817b2b18fdd231d309fe24e26b19a5556ef6cdd/ijson-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3c5f660658f2ebfba5d4dfe4bafe8cd3a0defcda410ec08d2205fe08c398940", size = 211967, upload-time = "2026-07-06T17:37:16.484Z" }, + { url = "https://files.pythonhosted.org/packages/dc/53/4c754c3ba18ec70b7086b91a4abd368358fc47cc9b3871afd50deef4fea1/ijson-3.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:29eb8f0c77a296a10843a1714ad4a5d561e604cda3c88585e9012cf2c1729b0a", size = 201020, upload-time = "2026-07-06T17:37:18.017Z" }, + { url = "https://files.pythonhosted.org/packages/26/2d/3e7191b3222a31c378b827565b4fa64676a293441279f84db3d971720bf5/ijson-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85997568d6b304cfa59d5c3f2b04f95b92e9a8c7f57d312343a7989cf8dfff85", size = 205584, upload-time = "2026-07-06T17:37:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/55ae9c915e68f37c8698f8b09355071dc808ced5e9d4abf8238dc363f500/ijson-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:c2e2509dc7f2fa5a2ac9ba7d15dd901f4093bd36b0784f65e04b681b7956651c", size = 54438, upload-time = "2026-07-06T17:37:20.656Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/5bf2656447f14a923d25a0401b1cd628ca05c23041d3a4c116ae8d44dc39/ijson-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2699e838099d056818c5f8e4ba702b345d0304e58847bdc79c5c1616d5d750a5", size = 56467, upload-time = "2026-07-06T17:37:21.615Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e4/dec06e84fac704039625039c6b116a44f17ad72fda48b8f88a2493364b77/ijson-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:c388f85cbb9eec022b2bdedd23ffacfe7ab100c1200b1f47bee6e6ea2c3309fa", size = 55774, upload-time = "2026-07-06T17:37:22.958Z" }, + { url = "https://files.pythonhosted.org/packages/49/ea/f42470cc773c8686dd0823da8aefc31a138cd9aea1ad476d43c8293068da/ijson-3.5.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:077b1b0bcb6a622d460c6674fe6647c7af5a3b06503e1996d1efcf9f78c94512", size = 57830, upload-time = "2026-07-06T17:37:37.005Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2f/64c61edab2c5ecf42a524146a70fa6171c8cf3960b947fb4c5f175660cb3/ijson-3.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e8dbf71b21e65cb7f0d4d387c07fe73be820168070c3be05a0763a80f424f1c7", size = 57325, upload-time = "2026-07-06T17:37:38.017Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5b/553ea8f14dfc756d6b6c9be2e2231ab44877ce96408eb9da3bb3f11ddd13/ijson-3.5.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7c5025a820f36f3e0e64f4b0232b338c690664c12b497e205cf64dcc64fc12", size = 71344, upload-time = "2026-07-06T17:37:38.997Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3e/0248fd00746731074ca01365a25d8aa3c4d54642c8a14490d94f7550bda9/ijson-3.5.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa7a2c94e43c02e0482088e6ff997e2bd7b9a76e6f1d0fd70891b4b5ff51318f", size = 71335, upload-time = "2026-07-06T17:37:39.965Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b9/1f1259546cc875adad240c468515f428d3a79b3def3ced17be3cdfe29146/ijson-3.5.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69b5eef70240e9734c5a2fb5cc3742cae411fc833a66b9a50722b9eedb1e27de", size = 68728, upload-time = "2026-07-06T17:37:40.928Z" }, + { url = "https://files.pythonhosted.org/packages/ea/02/aafbf0c3e1468c7c0f607065363b49c381de7e4bb43ae6674684a3fafe92/ijson-3.5.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b75b6bf4b0dbb0df24947db6722cd5723ce8d6e6b13fddbfc98db312ba82237", size = 54922, upload-time = "2026-07-06T17:37:41.879Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/62/aa770a9307508d2a2a2c62d536a49347bffe9e55322db27838d3c93d0b07/imageio-2.37.4.tar.gz", hash = "sha256:e45cbc5e83502047fb138f7f585f7f105a136a57eea5f4b3cfc6ce1b52720bd3", size = 390173, upload-time = "2026-07-20T05:26:11.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/2d/ca050652104bab2cf55e569db2a178b1b61cb041fef28307f2db383f6d9f/imageio-2.37.4-py3-none-any.whl", hash = "sha256:1ab2e22c8debf700f24c3ac43e8f95f3b3a8110c83b93411e97b4b0b2cd1c7e6", size = 318000, upload-time = "2026-07-20T05:26:09.874Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "imbalanced-learn" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sklearn-compat" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/35/d12fc1e8e2c2d8862104c4527641fe2d839324c50db0e6340dc73513faba/imbalanced_learn-0.14.2.tar.gz", hash = "sha256:f80ce7eafbcece8686e32571bd12978546c729c3f277215bead61a906ce9afe4", size = 19172446, upload-time = "2026-06-07T21:41:16.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/54/760ccac7d8feeea0c191767a6b025d3ca5014084443ff48afb6fb24ef056/imbalanced_learn-0.14.2-py3-none-any.whl", hash = "sha256:f9b81c47231aa1e3a71a1e4b3cc85b42e3b14f85e3a36922f3323c4da23605ef", size = 236073, upload-time = "2026-06-07T21:41:12.384Z" }, +] + +[[package]] +name = "imblearn" +version = "0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imbalanced-learn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/0a/f83099534a77757abf27427d339590c83cc68c3386690d4741d6454e185f/imblearn-0.0.tar.gz", hash = "sha256:d8fbb662919c1b16f438ad91a8256220e53bcf6815c9ad5502c518b798de34f2", size = 945, upload-time = "2017-01-19T11:52:35.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/a7/4179e6ebfd654bd0eac0b9c06125b8b4c96a9d0a8ff9e9507eb2a26d2d7e/imblearn-0.0-py2.py3-none-any.whl", hash = "sha256:d42c2d709d22c00d2b9a91e638d57240a8b79b4014122d92181fcd2549a2f79a", size = 1874, upload-time = "2017-01-19T11:52:37.416Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "iso8601" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/f3/ef59cee614d5e0accf6fd0cbba025b93b272e626ca89fb70a3e9187c5d15/iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df", size = 6522, upload-time = "2023-10-03T00:25:39.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545, upload-time = "2023-10-03T00:25:32.304Z" }, +] + +[[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 = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +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 = "kink" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/9b/71623fd68aabf7ee785e2a4032dbccc5121d7e27db0d72125739d1ce602b/kink-0.9.0.tar.gz", hash = "sha256:febe96b17f5e071858595ed536cc046fd0377596db296137773e4eeaa344800e", size = 13269, upload-time = "2026-03-19T08:26:54.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/c4/1c8fa2e8480c846bd9df3b39c53f987ae85575604acf8cd9373a5c860ae4/kink-0.9.0-py3-none-any.whl", hash = "sha256:ae4a7f644ed94ecaf94517d3c95b0f31332e11b9941e90a3f72898067020a494", size = 11627, upload-time = "2026-03-19T08:26:55.837Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, +] + +[[package]] +name = "liac-arff" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/43/73944aa5ad2b3185c0f0ba0ee6f73277f2eb51782ca6ccf3e6793caf209a/liac-arff-2.5.0.tar.gz", hash = "sha256:3220d0af6487c5aa71b47579be7ad1d94f3849ff1e224af3bf05ad49a0b5c4da", size = 13358, upload-time = "2020-08-31T18:59:16.878Z" } + +[[package]] +name = "librt" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/2f/ec5241c38e7fa0fe6c26bfc450e78b9489a6c3c08b394b85d2c10e506975/librt-0.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5", size = 148654, upload-time = "2026-07-08T12:24:30.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1a/d651e18d3ee7aa2879322368c4f278bb7ecaa6b90caadfdec4ebfa8389f3/librt-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547", size = 153537, upload-time = "2026-07-08T12:24:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/45/18/10bff2122577246009d9619b6569596daf69b7648812f997ca9ca0426f60/librt-0.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2", size = 494336, upload-time = "2026-07-08T12:24:33.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/87dfee871b852970f137fdeae8e2ca356c5ab38e6f21d2a3299535fc3159/librt-0.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929", size = 485393, upload-time = "2026-07-08T12:24:34.324Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d5/625447a8c0441ff5f15f4ac5e1d323fb9d4d256ebfde7a3c8e003f646057/librt-0.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a", size = 515382, upload-time = "2026-07-08T12:24:35.575Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d8/1c8c49ea04235960426444deece9092a6b3a9587a850a81bae2335317411/librt-0.13.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac", size = 509483, upload-time = "2026-07-08T12:24:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/6f/65/f1760fc48050e215201a03506c32b7270159088d01f64557b53e39e74a45/librt-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7", size = 532503, upload-time = "2026-07-08T12:24:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/18/1b/793e281dcf494879eff99f642b63ebc9c7c58694a1c2d1e93362a22c7041/librt-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40", size = 537027, upload-time = "2026-07-08T12:24:39.34Z" }, + { url = "https://files.pythonhosted.org/packages/69/45/0801bbb40c9eea795d3dd3ce91c4c5f3fe7d42d23ec4be3e8cb283bcc754/librt-0.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a", size = 517100, upload-time = "2026-07-08T12:24:40.907Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6c/eb5f514f8e29d4924bc0ff4601dd7b4175557e182e7c0721e84cffa39b8a/librt-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde", size = 558653, upload-time = "2026-07-08T12:24:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/f140100d1b59fe87ff40b5ecbb4e27924335b189a784e230ee465452f6c2/librt-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8", size = 104402, upload-time = "2026-07-08T12:24:43.668Z" }, + { url = "https://files.pythonhosted.org/packages/22/7c/57e40fef7cfb61869341cb28bdcefe8a950bebcbecca74a397bae14dce4a/librt-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc", size = 125002, upload-time = "2026-07-08T12:24:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" }, + { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" }, + { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" }, + { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" }, + { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" }, + { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" }, + { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" }, + { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" }, + { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" }, + { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" }, +] + +[[package]] +name = "lightgbm" +version = "4.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/8e/4db5e29290d7e619c307fdb8dab0a0514090af2ce3ec483050e024ec6126/lightgbm-4.7.0.tar.gz", hash = "sha256:f8e20f682c9aabd000bcf4a7ed8aa6f473c1adfecccae34ec24e823d156f4af0", size = 1792896, upload-time = "2026-07-18T21:00:56.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/05/7213965863cba1ed0150ad045bceed6276a1afaaaedbaeff4699ec4f0ccb/lightgbm-4.7.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:dfc1cfe8e760387be1e7ba7a214688be21fdff96e4ed9749188f83e1877c2477", size = 1877851, upload-time = "2026-07-18T21:00:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/b2/86/f4fe714f2e0bf3941705a20d7f6849dc476276d71236e82ea6b0d6539b86/lightgbm-4.7.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:129535462686f274df179133643118c5c5c5667167fe6c3a28d955f0b3c8e868", size = 1498914, upload-time = "2026-07-18T21:00:36.549Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/b29580948b92e8c2f84dea70118ac702ff067dc52ec4ffb5d73c953536a5/lightgbm-4.7.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4529acec5c6fefe4768302a529707d0ead90f6a6f42df694b856212e09695b8", size = 3349492, upload-time = "2026-07-18T21:00:37.943Z" }, + { url = "https://files.pythonhosted.org/packages/15/eb/837ea3b40cc36e22eeebb9785c01e42b2c255d033eea1d2d9ee8e2540e55/lightgbm-4.7.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d23e922acd891e77212e4d0fbcee9ba973c96dee479491341d05ba595357ebb7", size = 3476028, upload-time = "2026-07-18T21:00:39.331Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0b/c5c17d862b12ce292f24cd85d40f2f8f8981668fbdbd43fdc2625eccbc79/lightgbm-4.7.0-py3-none-win_amd64.whl", hash = "sha256:f42d1e5b32b6f170e606d7c689c6165671da98d7bf37f1addec2623efc8740c9", size = 1360833, upload-time = "2026-07-18T21:00:40.865Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, +] + +[[package]] +name = "lime" +version = "0.2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/86/91a13127d83d793ecb50eb75e716f76e6eda809b6803c5a4ff462339789e/lime-0.2.0.1.tar.gz", hash = "sha256:76960e4f055feb53e89b5022383bafc87b63f25bac6265984b0a333d1a57f781", size = 275719, upload-time = "2020-06-26T21:38:15.46Z" } + +[[package]] +name = "llama-cpp-python" +version = "0.3.34" +source = { registry = "https://abetlen.github.io/llama-cpp-python/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "diskcache" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cpu') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-linux_riscv64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-macosx_11_0_arm64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-musllinux_1_2_aarch64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-musllinux_1_2_x86_64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-pyemscripten_2026_0_wasm32.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.34/llama_cpp_python-0.3.34-py3-none-win_amd64.whl" }, +] + +[[package]] +name = "llama-cpp-python" +version = "0.3.34" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "diskcache" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/2f/46487e949da42b31847853793ddbfd44c7414e94d4364c9a41f91f30af27/llama_cpp_python-0.3.34.tar.gz", hash = "sha256:d849d286d808284f1d3ec1bd6875572430d29d1f9574a010232caa4e9cef0e35", size = 71617302, upload-time = "2026-07-12T04:33:41.928Z" } + +[[package]] +name = "llvmlite" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/a0/acc8ffcd5bdc63df0097e22c719bfcd61b604358343089313a8aebbb24ab/llvmlite-0.48.0.tar.gz", hash = "sha256:543b19f9ef8f3c7c60d1468191e4ee1b1537bf9f8a3d56f64c0ddd98de92edd2", size = 184016, upload-time = "2026-07-02T20:20:05.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/4e/32543c42568fb321b3bdfcf9106e4116ab8f5a7bbcfd9ecf5569b0c07d83/llvmlite-0.48.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:614aad57df707e3172efd5165f2aa7da6a0c6897e40dce590bf756396815ba76", size = 40480650, upload-time = "2026-07-01T18:41:01.945Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0d/6aa48abd423067139a129d1434b77bbcc56080db51d12a88510bb491ca3d/llvmlite-0.48.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13532f248960ba888ad5ab8150494e2f3a3d20e5f59f264e63741ea5b0ba844c", size = 59890118, upload-time = "2026-07-01T18:41:10.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c7/aa917444d871a79608af49149de1b28764e87d2ab41f933c5cd02431d03d/llvmlite-0.48.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ee0c77685a18f5fca994ae21d0763007fca5c5c64b41de37accc78b69079176", size = 58343459, upload-time = "2026-07-01T18:41:06.21Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2b/ceee1cdc263617109d514ac4d1b31f10a282662740ff7d5777baae25b3b5/llvmlite-0.48.0-cp310-cp310-win_amd64.whl", hash = "sha256:02853fe4214acb3780fc920c3fee10564b61d58a35e1b78afcc8a546c2deaba3", size = 41864734, upload-time = "2026-07-01T18:41:14.746Z" }, + { url = "https://files.pythonhosted.org/packages/9a/55/595981f14fbae9ba966feb12af552b1fe69889e44e64ac883a731ed335e0/llvmlite-0.48.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:56a7e24607d3f02d7b1bae8d29c7e1e423d53143d68b072999777f19678fe77b", size = 40480651, upload-time = "2026-07-01T18:41:18.438Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/0109d1b9cb3f4603f3890e30bc66c65332b79185f12a045343b2ae431f67/llvmlite-0.48.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6fa532d6bb3fd3f0803567c736401c54aecfe1a396d3ad25d2440d220e09f0e7", size = 59890118, upload-time = "2026-07-01T18:41:28.184Z" }, + { url = "https://files.pythonhosted.org/packages/02/eb/c5281be180c789cdffbf45b671884c57d7e61345ef3b0f643a4965e108e8/llvmlite-0.48.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:979a66a3f28a02565383ff463527dce78e9b856298872a361283132488e83591", size = 58343458, upload-time = "2026-07-01T18:41:23.397Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f7/b3222b13f2d424dae3c9e63fde476af25ebccf1f3faf0b52d1b79fc15c70/llvmlite-0.48.0-cp311-cp311-win_amd64.whl", hash = "sha256:efaee0276e5e17c2b99b92e0c974bd484ef5977cf5dbc9168e82b71578edb47f", size = 41864734, upload-time = "2026-07-01T18:41:31.932Z" }, + { url = "https://files.pythonhosted.org/packages/92/a2/28696a9e61e245d1a79816d29d106692a90a2b6e7d78c98b326db70827af/llvmlite-0.48.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:d66c3beb4209087ddd4cf4ed2a0856b6887e6a913bdcf1aacfec9851cf2cba4e", size = 40480651, upload-time = "2026-07-01T18:41:35.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/f2/72409351db66d0a317ec5087e076f31fb7b773a640db8a90ce6b5cac9edd/llvmlite-0.48.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:416fa4c2c66c2c6dc6d0a402648c19206e548efa0aa1eff01ad5cdad0af8217d", size = 59890118, upload-time = "2026-07-01T18:41:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/3a/27/5ae2f3722606360480707adb47f001ad89df8251d06b14ee80336e660b66/llvmlite-0.48.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5e5a5131045b72345c71062ea1a91910dde913792b6c9b28ebb2c1c0a712e98", size = 58343459, upload-time = "2026-07-01T18:41:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/16/78/d824ffff7521cd140dc2006e44ce2bc82e64b48d1b32e90e956308c85a74/llvmlite-0.48.0-cp312-cp312-win_amd64.whl", hash = "sha256:d45c7541a80934ec6d8ab0defe67439494ecd2193cbf852a44ba827808976ac1", size = 41865022, upload-time = "2026-07-01T18:41:48.663Z" }, + { url = "https://files.pythonhosted.org/packages/9c/23/fe9316d14626b42c73ef0b502e724705a6ee9450afe53759c0a99c37c2d7/llvmlite-0.48.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a83a99ef0c05b4ccddf9b6218ed9fe84b653a0caf7c1d9dbe148d6d16c67f518", size = 40480652, upload-time = "2026-07-01T18:41:52.216Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4a/90715fa12006d681270b08d881195b6fab3ec39572e048764a1f7f59fed7/llvmlite-0.48.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8761b9e522f55207e24424fcd98370289eec2710bf8e915c82d1053f642450dc", size = 59890120, upload-time = "2026-07-01T18:42:00.748Z" }, + { url = "https://files.pythonhosted.org/packages/70/5e/7b3e20d64650ca3c80af0cdb664ec4b575ec83d9d4dd05bea8bd31f9bbb6/llvmlite-0.48.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fe5cb59b2063bfa039dcb8ca6481c0181bf552f340d10dcf61d7996a665556e", size = 58343457, upload-time = "2026-07-01T18:41:56.41Z" }, + { url = "https://files.pythonhosted.org/packages/17/97/5a430055d1838cf1fb7a01cfa943300f5e4c026fc6333a522c5e4a03b0c1/llvmlite-0.48.0-cp313-cp313-win_amd64.whl", hash = "sha256:91c7e24e74cde3f02b88aa5acca678373f9e069f3b98531b3dbb3a142d9d10bb", size = 41865022, upload-time = "2026-07-01T18:42:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8e/8170f2e0c217f88069c333d85bb976e536b332aecfcce606ddbdb249385f/llvmlite-0.48.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:321f1ac39b462603f0b589751aecf2d237d056f6d005749c1752b6f23ec3f074", size = 40480650, upload-time = "2026-07-01T18:42:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e1/05b50692b647cac3c18200ac485b04f342f00ed173c9cc46767274469a15/llvmlite-0.48.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:05f0103c8f2f96a37441337e3643863c01b8e83e530aff38960dcb383c54a065", size = 59890115, upload-time = "2026-07-01T18:42:17.805Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c3/470b8c4ff9ae2db2f9cf5c3e73de76ed908a32788ae9eb5602d43e6a476b/llvmlite-0.48.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37d66fae72802175b0bfe1ea06e624b51e2d7aee6c3c34bbd09739b8f88e8e0b", size = 58343457, upload-time = "2026-07-01T18:42:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2d/6a5171fb7236ac0895e1a02ccba3735bf291e8597239aa6421894d3c0ba8/llvmlite-0.48.0-cp314-cp314-win_amd64.whl", hash = "sha256:966dcab0a598e2bd8fb5f2cc082cf7b07bae564fc485a3a8692393caf986facf", size = 42986372, upload-time = "2026-07-01T18:42:21.483Z" }, + { url = "https://files.pythonhosted.org/packages/94/e3/7a93e09c9f94e637ca90209ceef0334a9a1d45b0bdb7c92ff922d25d6187/llvmlite-0.48.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a5c413317050a1d67c34708bde97707f9b2257ef1017f7532d21fe7d9a9ff30", size = 40480654, upload-time = "2026-07-01T18:42:25.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/98/a29133b4728671a175f7d616fab8b1c6e1d8c269d1523581d3160697bfb1/llvmlite-0.48.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d9f952dff6c350c529997423d4fa43abae9722a884ac7ffafc37a3af676e7db", size = 59890119, upload-time = "2026-07-01T18:42:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/1a/cf/7aac11a1f1c7ec54b60c7f6814e87561fb6b55b2f290455d7941eb113420/llvmlite-0.48.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:054aa7d46595935565f276cf0c1659b4f10929c996dd4a606875fae26fba2a23", size = 58343460, upload-time = "2026-07-01T18:42:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/db/41/b96f440c7df5ebba07872cad4e30fbc3560387755b1ea0b629adb76d5ca8/llvmlite-0.48.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d0b3c61aac83b42fb48cc96bffbf57c81b82b2aa92276b7ed6420c814629a99a", size = 42986383, upload-time = "2026-07-01T18:42:37.544Z" }, +] + +[[package]] +name = "logical-unification" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "multipledispatch" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/5d/37673e494a4eed550785ad1268df0202e69aa081bcbf7c0aafd0a853b0fc/logical_unification-0.4.7.tar.gz", hash = "sha256:3d73b263a870827b3f52d89c94f3336afd7fcaecf1e0c67fa18e73025399775c", size = 13513, upload-time = "2025-10-20T21:42:24.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/d0/337b3c49cbe742ab5c118d14730fbc7b14b57d1a130d4f39efaa9ec04226/logical_unification-0.4.7-py3-none-any.whl", hash = "sha256:077f49e32693bc66a418f08c1de540f55b5a20f237ffb80ea85d99bfc6139c3b", size = 13469, upload-time = "2025-10-20T21:42:24.024Z" }, +] + +[[package]] +name = "lxml" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/da/dbe4dfc01ac226fb0504fad035f4d69f3202f3502e20e68537631daddd96/lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", size = 8541124, upload-time = "2026-05-18T19:17:11.589Z" }, + { url = "https://files.pythonhosted.org/packages/78/20/f7095ed9fc2c025f9cfe71cc6ec9f1feb05624edc1812423b5f1aecf3d4b/lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", size = 4602783, upload-time = "2026-05-18T19:17:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a4/65c63ca98bd129f6cff7b8c2fa48953ab058cc6005b541354e7dd54d8000/lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", size = 5002687, upload-time = "2026-05-18T19:17:01.738Z" }, + { url = "https://files.pythonhosted.org/packages/96/1d/ab7a5c4b5a394d98a94e2d0fc67bab8297597426770dd4978370fbdaf531/lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", size = 5155099, upload-time = "2026-05-18T19:17:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/07603bfeeb891a2596d5c2a68f7d2f70f7d11c841ebe391412c69c2857b0/lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", size = 5057225, upload-time = "2026-05-18T19:17:08.117Z" }, + { url = "https://files.pythonhosted.org/packages/7a/16/cb391ee4b90186fa16d9ebcbe3ea96c71b8da3b0686386c8dcbcc3c67d44/lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", size = 5287643, upload-time = "2026-05-18T19:17:11.507Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d6/b619717f918fd76747448fdbaee0e769edbc70e659b5b5d0112b7020b7a3/lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", size = 5412445, upload-time = "2026-05-18T19:17:22.182Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/12bc5390ac0a3edeb579d9535e5049a5dda663438728e179d52fb319c33a/lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", size = 4770864, upload-time = "2026-05-18T19:17:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/6500c09da3137f54f020e908d81cfc5ee3e8888e908fd380207afad7c2e6/lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", size = 5359594, upload-time = "2026-05-18T19:17:32.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9b/f64b4cc6b7ebcf75d95af3cde934d254b5f2f10d4163928d838d86b6eb48/lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", size = 5107713, upload-time = "2026-05-18T19:17:04.402Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/c7388ad5d3a72315d2832dc1458cbf4f2af7f2b990b606ff4876efd04511/lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", size = 4803973, upload-time = "2026-05-18T19:17:06.545Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/76197f0bbf165f0b9e75be59be4997e5259cde973f12f098c1b54c7f5d60/lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", size = 5349925, upload-time = "2026-05-18T19:17:09.743Z" }, + { url = "https://files.pythonhosted.org/packages/24/52/d2a0cfeccb9bcdc47c7ee05cdae5d69b48c9acf20997790a6338bb0d0b3b/lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", size = 5309825, upload-time = "2026-05-18T19:17:13.831Z" }, + { url = "https://files.pythonhosted.org/packages/19/4a/b30944266776c2f49749ef2445aa7e78898194134b80ad776386f61b56ae/lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", size = 3598402, upload-time = "2026-05-18T19:17:08.21Z" }, + { url = "https://files.pythonhosted.org/packages/9e/97/33691c66a4d7ec1a5a98e7c909a5b83ee45c7f7ba4cf92b1c4cf26e98079/lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", size = 4021295, upload-time = "2026-05-18T19:17:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5f/26a4dd0e12b9456ff7b12a21af5b491eb6629680d1edd73f4140fd386bcf/lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", size = 3667717, upload-time = "2026-05-19T19:22:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, + { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" }, + { url = "https://files.pythonhosted.org/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, + { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" }, + { url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" }, + { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" }, + { url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" }, + { url = "https://files.pythonhosted.org/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" }, + { url = "https://files.pythonhosted.org/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" }, + { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, + { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, + { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, + { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, + { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, + { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" }, +] + +[[package]] +name = "mako" +version = "1.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cycler", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fonttools", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "kiwisolver", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyparsing", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cycler", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fonttools", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyparsing", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "minikanren" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cons" }, + { name = "etuples" }, + { name = "logical-unification" }, + { name = "multipledispatch" }, + { name = "toolz" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3d/bbab3c19771efbfafc52de98db8ad7cf3c2c444bbbd7241c2b06e9f305bc/minikanren-1.0.5.tar.gz", hash = "sha256:c030e3e9a3fa5f372f84b66966776a8dc63b16b98768b78be0401982b892e00d", size = 21699, upload-time = "2025-06-24T21:38:51.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/02/5e9ae831946db26f172e03e896fe83b07c5ca643df2b32c1b81557f0e77f/minikanren-1.0.5-py3-none-any.whl", hash = "sha256:22c24f4fdf009a56e30655787af45c90f0704bcc24e8d3e651378675b4bccb21", size = 24072, upload-time = "2025-06-24T21:38:50.113Z" }, +] + +[[package]] +name = "minio" +version = "7.2.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "certifi" }, + { name = "pycryptodome" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/df/6dfc6540f96a74125a11653cce717603fd5b7d0001a8e847b3e54e72d238/minio-7.2.20.tar.gz", hash = "sha256:95898b7a023fbbfde375985aa77e2cd6a0762268db79cf886f002a9ea8e68598", size = 136113, upload-time = "2025-11-27T00:37:15.569Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multipledispatch" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/3e/a62c3b824c7dec33c4a1578bcc842e6c30300051033a4e5975ed86cc2536/multipledispatch-1.0.0.tar.gz", hash = "sha256:5c839915465c68206c3e9c473357908216c28383b425361e5d144594bf85a7e0", size = 12385, upload-time = "2023-06-27T16:45:11.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c0/00c9809d8b9346eb238a6bbd5f83e846a4ce4503da94a4c08cb7284c325b/multipledispatch-1.0.0-py3-none-any.whl", hash = "sha256:0c53cd8b077546da4e48869f49b13164bebafd0c2a5afceb6bb6a316e7fb46e4", size = 12818, upload-time = "2023-06-27T16:45:09.418Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/e0/a77ca96e772e13c828fa52f3ad370d413bef194aeaf78b7c6611870ad815/multiprocess-0.70.15.tar.gz", hash = "sha256:f20eed3036c0ef477b07a4177cf7c1ba520d9a2677870a4f47fe026f0cd6787e", size = 1894495, upload-time = "2023-07-23T05:03:29.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/73/3dcd3175c0295d3989859197c8a9111d5936693a0ea639dc80de87bb7803/multiprocess-0.70.15-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aa36c7ed16f508091438687fe9baa393a7a8e206731d321e443745e743a0d4e5", size = 134981, upload-time = "2023-07-23T05:03:03.746Z" }, + { url = "https://files.pythonhosted.org/packages/35/a8/36d8d7b3e46b377800d8dec47891cdf05842d1a2366909ae4a0c89fbc5e6/multiprocess-0.70.15-py310-none-any.whl", hash = "sha256:7dd58e33235e83cf09d625e55cffd7b0f0eede7ee9223cdd666a87624f60c21a", size = 134824, upload-time = "2023-07-23T05:03:21.182Z" }, + { url = "https://files.pythonhosted.org/packages/e7/41/96ac938770ba6e7d5ae1d8c9cafebac54b413549042c6260f0d0a6ec6622/multiprocess-0.70.15-py311-none-any.whl", hash = "sha256:134f89053d82c9ed3b73edd3a2531eb791e602d4f4156fc92a79259590bd9670", size = 135392, upload-time = "2023-07-23T05:03:22.885Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3f/8354ce12fd13bd5c5bb4722261a10ca1d6e2eb7c1c08fa3d8a4e9dc98f44/multiprocess-0.70.15-py37-none-any.whl", hash = "sha256:f7d4a1629bccb433114c3b4885f69eccc200994323c80f6feee73b0edc9199c5", size = 116276, upload-time = "2023-07-23T05:03:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a6/c5cb599d917904878f220a4dbdfdcc4ef291dd3956c35b3b0dc6fc42fb6d/multiprocess-0.70.15-py38-none-any.whl", hash = "sha256:bee9afba476c91f9ebee7beeee0601face9eff67d822e893f9a893725fbd6316", size = 132626, upload-time = "2023-07-23T05:03:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c9/820b5ab056f4ada76fbe05bd481a948f287957d6cbfd59e2dd2618b408c1/multiprocess-0.70.15-py39-none-any.whl", hash = "sha256:3e0953f5d52b4c76f1c973eaf8214554d146f2be5decb48e928e55c7a2d19338", size = 133349, upload-time = "2023-07-23T05:03:27.022Z" }, +] + +[[package]] +name = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/09/f2f5f45dae0c9a0891e4751a73312730e009395102e5d72a22a976cca41f/mypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc", size = 14927774, upload-time = "2026-07-13T11:28:38.224Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/345367effd3a6877275a94d481614bfca983f45e028c6290e2cc54603811/mypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3", size = 14000127, upload-time = "2026-07-13T11:30:19.57Z" }, + { url = "https://files.pythonhosted.org/packages/99/6c/a10b7a7b9f0a755fb94e27ae834d4cea9ad6c5221f9325eef8f182641feb/mypy-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805", size = 14229437, upload-time = "2026-07-13T11:28:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bd/a26a602acb1bbf849fa4bdac4bc657ee2f11c0c2a764a2cc87a5304e865c/mypy-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117", size = 15171457, upload-time = "2026-07-13T11:29:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/124f462bef69bcbc90b9358088460b6091954a3e004852fcd9948db617a5/mypy-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5", size = 15478281, upload-time = "2026-07-13T11:32:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/8bdca6a8ac8d856d82ed049144af2721245a135c2e8001d3890c93975852/mypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494", size = 11148008, upload-time = "2026-07-13T11:34:17.332Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/490eea348e60ba50decec20bc750605444149a5d7a8cc560042f90ba2c75/mypy-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee", size = 10142329, upload-time = "2026-07-13T11:32:52.116Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" }, + { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" }, + { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" }, + { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" }, + { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "narwhals" +version = "2.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/1d/58946e5aab18393e793bd4add6985b95d0e01c3a2d832f38f54468b10dcd/narwhals-2.24.0.tar.gz", hash = "sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d", size = 661143, upload-time = "2026-07-13T10:49:19.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/85/a5bfaebfd305ac18b57b0854d74e37e586809061a91fda62f0bd50c8518e/narwhals-2.24.0-py3-none-any.whl", hash = "sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489", size = 461030, upload-time = "2026-07-13T10:49:17.571Z" }, +] + +[[package]] +name = "netaddr" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/90/188b2a69654f27b221fba92fda7217778208532c962509e959a9cee5229d/netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a", size = 2260504, upload-time = "2024-05-28T21:30:37.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cc/f4fe2c7ce68b92cbf5b2d379ca366e1edae38cccaad00f69f529b460c3ef/netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe", size = 2262023, upload-time = "2024-05-28T21:30:34.191Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numba" +version = "0.66.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/48/d139bde40f2359351bfe26ee1b261937f458ac177ab810d4f045ae1c9d92/numba-0.66.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:27951c47e0def9bf8afe580eb961102902e2fd23cb77924b7d9d7cc0f8b444cb", size = 2727368, upload-time = "2026-07-01T23:12:04.282Z" }, + { url = "https://files.pythonhosted.org/packages/36/e4/b780bfa9191410da50ba249cb3248a75014e17f611e72709cbddcb21f42d/numba-0.66.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc408c54b450f41582f4be1608f8981c1dcc44c7f40355cc150dd93015753407", size = 3803554, upload-time = "2026-07-01T23:12:06.379Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b2/a051b96626bdf5c4d8fa6b8d450605c09638d85dc872ab63ef9a67096dca/numba-0.66.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c14c044c06b453ec3fa7715dfe75425e2ba72c73377a7ffde6d9ec511dfd94c", size = 3510065, upload-time = "2026-07-01T23:12:08.051Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/24dcdc3e919522e2efbd92969c281ff40deb1d5f8a994bcd0057081c158c/numba-0.66.0-cp310-cp310-win_amd64.whl", hash = "sha256:2338cc0d43609fe448930848fd35a5bc688761b986f81b597a6f45cc0f8c9577", size = 2780379, upload-time = "2026-07-01T23:12:09.772Z" }, + { url = "https://files.pythonhosted.org/packages/9e/02/970796b4daa709604cde22e87a7cda9bde473c278ea4a75f59fe38cee47f/numba-0.66.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bbd531c327557a9004507fa6bff06c53ab51a7a5776b75261bb9cef1efe2b2ea", size = 2727049, upload-time = "2026-07-01T23:12:11.296Z" }, + { url = "https://files.pythonhosted.org/packages/8c/99/33a6ed9c1a0b5e42efa98eb0edf617d61dca576c82625947377b1d4540c9/numba-0.66.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc6629becb21a867d85401ec89f426dd24c484a4193ade8a38309debfd1529ca", size = 3808870, upload-time = "2026-07-01T23:12:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/04/20/8c51126025211659235b8de2866dfa226984ae0c8273461a3cf374716741/numba-0.66.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac69f3ccb8af100f5913c1241edc9692bad1cdd2508721713f426eb06c9a659", size = 3514498, upload-time = "2026-07-01T23:12:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c9/9476940bc6d5caf5c0cf2e4c5feecbf01244bbe6f914614082dd7a3e520e/numba-0.66.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb601841d9e02e6237bb6522e36d0741614be3cfe2b482a6f00a41b5ba209443", size = 2780225, upload-time = "2026-07-01T23:12:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/62/a3/70deb7f88461c1cd5d16aa990c2380604102661a427667b8950dcdccc27f/numba-0.66.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53ca5900b7cab15109796030113a6b28576bae5ad7bb507ad6dd1360ddd81ba4", size = 2727264, upload-time = "2026-07-01T23:12:18.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/25c319845e9a4e08f16611ddbda56a192eb7b6ed13e1a2bff2da272ffb97/numba-0.66.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0999e3ee1b18c48e1fb51d11af35ef59852c7f4f50569c9550c25faef0616ad1", size = 3866252, upload-time = "2026-07-01T23:12:20.429Z" }, + { url = "https://files.pythonhosted.org/packages/71/ef/a82d6fd6bf1b0fe461651e924d3647eeec9ac17f8eee4896264bf7480930/numba-0.66.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efe0d2d5099790df945e0cb6e1b3104bd965d7bbfac50d62f1d5d1d6ade0825d", size = 3566974, upload-time = "2026-07-01T23:12:22.116Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/9e6171e378822ab191c7abcfd3d8cfc8644516f6c7834c22e210e4acc070/numba-0.66.0-cp312-cp312-win_amd64.whl", hash = "sha256:b075a4e7ebc43dc6294f223e2821659656209fd5e0ce53245877c23d66d6e1a9", size = 2797403, upload-time = "2026-07-01T23:12:23.724Z" }, + { url = "https://files.pythonhosted.org/packages/03/52/176c02d005c5c5143cde10a85bbcdcb6236d9e34c3aac089380e0506cd1d/numba-0.66.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:380b2556a2019ccd1e956ae77dd257eaa39403f7520768b626d44b755112785e", size = 2727084, upload-time = "2026-07-01T23:12:25.434Z" }, + { url = "https://files.pythonhosted.org/packages/44/b5/e930010965568fe7f2c6c962fd2849d458cb9f62c3ab7584af8a19a2b40a/numba-0.66.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:939316d5d8619751207b8972a67852b5a7646665298cb4de693cd6bf135152f4", size = 3873663, upload-time = "2026-07-01T23:12:27.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ec/5b51457cbe96e4831141d83e892e65191b23a1b78728456c62909d231ace/numba-0.66.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdf506775d9f02eb92a87bf5c5b1e0d25506fd18cafd769f4ed914a8feac73e7", size = 3573529, upload-time = "2026-07-01T23:12:28.944Z" }, + { url = "https://files.pythonhosted.org/packages/83/7e/cea7710e96913d3c7f2999f16db1b28e6c5be5171cbf40f77f98333a7243/numba-0.66.0-cp313-cp313-win_amd64.whl", hash = "sha256:c5bfe5350284509ab0474390321454c3a8627a188af5b68c910e83df3e2db4a7", size = 2797247, upload-time = "2026-07-01T23:12:30.774Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/7e0e73550eb4e41ede6e72fb5371f4539537a4d770a3b73fa9b61aea0622/numba-0.66.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:46ae5f2b19e2af3c33c2df100306a90ea2f981c8158b0390f8bf6c20eee7357e", size = 2727296, upload-time = "2026-07-01T23:12:32.39Z" }, + { url = "https://files.pythonhosted.org/packages/0f/26/885774c006de6620ed3d10f45d8e20fe0b8e6aad6d573211a2cbc8b3e528/numba-0.66.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e2b101f23b8b63978d334574d2039f27f0dccfe1d891756f33a2e2f3e4c88cf4", size = 3842720, upload-time = "2026-07-01T23:12:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/edebf7de890b73973d839dd971cf73734adfb81ffa1b4504f84b9059c3e5/numba-0.66.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b943eb2c9ba371908ce2cd6dfc643db51fc40f7966993376a1701bc922f537", size = 3543537, upload-time = "2026-07-01T23:12:35.566Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/b46ad28ac3681d035ea21365c5e052149062e1a0a9affd0563d2760ea6ff/numba-0.66.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd57790acd20f6a468e0ad333ef6b82355e309a92310fb7dff80e919f01a21a9", size = 2799250, upload-time = "2026-07-01T23:12:37.154Z" }, + { url = "https://files.pythonhosted.org/packages/10/6f/5e77a7397a37dd16f57a7b72e7e470db5227b68e3639df0d13a8e674883d/numba-0.66.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:db7735d15ea17a283d6485b9fa3504769f78fd86e5146638ad5e8da57c031b9e", size = 2730342, upload-time = "2026-07-01T23:12:38.758Z" }, + { url = "https://files.pythonhosted.org/packages/39/fd/e9c9680a3813f3d781c20e5d53c1074801b787d4feecca0472fdd7c05ce1/numba-0.66.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:651a2b53298340956db26ecbe7ab043106b50a40c2807e66d617a4917245a4ab", size = 3878695, upload-time = "2026-07-01T23:12:40.302Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/9b363287b85fcd4537ea3878793822878b2ac1008a78159d2096fea628de/numba-0.66.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c1144ba1720ea59ad79f4f488ed54d149b2613b357f7e445678b7d0739c70e9", size = 3596323, upload-time = "2026-07-01T23:12:42.805Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f2/dca53d50b8f2289dd01954ace9da261e0487d5b74b188b4304e4ecc3492c/numba-0.66.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d426178fb991a85714c43112a8ea7b9d9579ea856ad8dcdb9c1c3941903ba5be", size = 2804772, upload-time = "2026-07-01T23:12:44.399Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +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 = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/45/9e/2f562daf80eb8f7a685fb7bea4fda71f6048e4f359d6fdd1b6e70206cb2f/nvidia_cublas-13.1.1.3-py3-none-win_amd64.whl", hash = "sha256:b6cdce694e47ff6aadf0a69df1cab6628d696f5ff56e8d16af50309d855fa20f", size = 404358158, upload-time = "2026-04-08T18:47:26.987Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/99/db44d685f0e257ff0e213ade1964fc459b4a690a73293220e98feb3307cf/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0", size = 590537124, upload-time = "2025-03-07T01:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/70/61/7d7b3c70186fb651d0fbd35b01dbfc8e755f69fd58f817f3d0f642df20c3/nvidia_cublas_cu12-12.8.4.1-py3-none-win_amd64.whl", hash = "sha256:47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af", size = 567544208, upload-time = "2025-03-07T01:53:30.535Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/b74b10025c1205695c5676373f2edd3e87a7202cc62ead0dfbc373b0f6ea/nvidia_cuda_cupti-13.0.85-py3-none-win_amd64.whl", hash = "sha256:683f58d301548deeefcb8f6fac1b8d907691b9d8b18eccab417f51e362102f00", size = 7736776, upload-time = "2025-09-04T08:38:08.38Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/1f/b3bd73445e5cb342727fd24fe1f7b748f690b460acadc27ea22f904502c8/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed", size = 9533318, upload-time = "2025-03-07T01:40:10.421Z" }, + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/41/bc/83f5426095d93694ae39fe1311431b5d5a9bb82e48bf0dd8e19be2765942/nvidia_cuda_cupti_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e", size = 7015759, upload-time = "2025-03-07T01:51:11.355Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/4a/af/345fedb9f4c76c84ab4fa445b36bd4048a4d9db60e6bc76b4f913ff4b852/nvidia_cuda_nvrtc-13.0.88-py3-none-win_amd64.whl", hash = "sha256:6bcd4e7f8e205cbe644f5a98f2f799bef9556fefc89dd786e79a16312ce49872", size = 76807835, upload-time = "2025-09-04T08:39:15.274Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d1/e50d0acaab360482034b84b6e27ee83c6738f7d32182b987f9c7a4e32962/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8", size = 43106076, upload-time = "2025-03-07T01:41:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/45/51/52a3d84baa2136cc8df15500ad731d74d3a1114d4c123e043cb608d4a32b/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909", size = 73586838, upload-time = "2025-03-07T01:52:13.483Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/b7/94/6b867483bec07da24ffa32736c79fabb94ef3a7af4d787a9d4a974868576/nvidia_cuda_runtime-13.0.96-py3-none-win_amd64.whl", hash = "sha256:f79298c8a098cec150a597c8eba58ecdab96e3bdc4b9bc4f9983635031740492", size = 2927037, upload-time = "2025-10-09T09:04:23.782Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/75/f865a3b236e4647605ea34cc450900854ba123834a5f1598e160b9530c3a/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d", size = 965265, upload-time = "2025-03-07T01:39:43.533Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/30/a5/a515b7600ad361ea14bfa13fb4d6687abf500adc270f19e89849c0590492/nvidia_cuda_runtime_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8", size = 944318, upload-time = "2025-03-07T01:51:01.794Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.19.0.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/b8/277c51962ee46fa3e5b203ac5f76107c650f781d6891e681e28e6f3e9fe6/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:08caaf27fe556aca82a3ee3b5aa49a77e7de0cfcb7ff4e5c29da426387a8267e", size = 656910700, upload-time = "2026-02-03T20:40:25.508Z" }, + { url = "https://files.pythonhosted.org/packages/c5/41/65225d42fba06fb3dd3972485ea258e7dd07a40d6e01c95da6766ad87354/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ac6ad90a075bb33a94f2b4cf4622eac13dd4dc65cf6dd9c7572a318516a36625", size = 657906812, upload-time = "2026-02-03T20:44:12.638Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a5/48f07449fc9c6cc146dcafe6149fa5d69630137d2ec5b7d9e09f255fadd7/nvidia_cudnn_cu12-9.19.0.56-py3-none-win_amd64.whl", hash = "sha256:cec70596b9ce878fab83810c3f5a2e606d35f510e5fee579759e4cbc68a23750", size = 644003014, upload-time = "2026-02-03T20:46:25.768Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/78/39/21507455b1bca8b5702a9e9fc6ce73735f216f558dac2c9ede58e4d456b8/nvidia_cudnn_cu13-9.20.0.48-py3-none-win_amd64.whl", hash = "sha256:af8139732b99c0118be65ea5aac97f0d46018f8c552889e49d2fb0c6261a4a24", size = 350712614, upload-time = "2026-03-09T19:31:11.398Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/f8af21a2ed1beed337a6a02c5a28aeb85441f4d578ec3d529543c775ea4b/nvidia_cufft-12.0.0.61-py3-none-win_amd64.whl", hash = "sha256:2abce5b39d2f5ae12730fb7e5db6696533e36c26e2d3e8fd1750bdd2853364eb", size = 213342123, upload-time = "2025-09-04T08:40:51.145Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a", size = 193109211, upload-time = "2025-03-07T01:44:56.873Z" }, + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ec/ce1629f1e478bb5ccd208986b5f9e0316a78538dd6ab1d0484f012f8e2a1/nvidia_cufft_cu12-11.3.3.83-py3-none-win_amd64.whl", hash = "sha256:7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7", size = 192216559, upload-time = "2025-03-07T01:53:57.106Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f5/5607710447a6fe9fd9b3283956fceeee8a06cda1d2f56ce31371f595db2a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a", size = 1120705, upload-time = "2025-03-07T01:45:41.434Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, + { url = "https://files.pythonhosted.org/packages/99/27/72103153b1ffc00e09fdc40ac970235343dcd1ea8bd762e84d2d73219ffa/nvidia_curand-10.4.0.35-py3-none-win_amd64.whl", hash = "sha256:65b1710aa6961d326b411e314b374290904c5ddf41dc3f766ebc3f1d7d4ca69f", size = 55242481, upload-time = "2025-08-04T10:30:41.831Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/5e/92aa15eca622a388b80fbf8375d4760738df6285b1e92c43d37390a33a9a/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd", size = 63625754, upload-time = "2025-03-07T01:46:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/b9/75/70c05b2f3ed5be3bb30b7102b6eb78e100da4bbf6944fd6725c012831cab/nvidia_curand_cu12-10.3.9.90-py3-none-win_amd64.whl", hash = "sha256:f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec", size = 62765309, upload-time = "2025-03-07T01:54:20.478Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparse", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, + { url = "https://files.pythonhosted.org/packages/99/ef/332a0101260ca78a1daef046bf0b06199e8ed4dac1d2aa698289c358169c/nvidia_cusolver-12.0.4.66-py3-none-win_amd64.whl", hash = "sha256:16515bd33a8e76bb54d024cfa068fa68d30e80fc34b9e1090813ea9362e0cb65", size = 193551444, upload-time = "2025-09-04T08:41:46.813Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparse-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0", size = 267229841, upload-time = "2025-03-07T01:46:54.356Z" }, + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/13/c0/76ca8551b8a84146ffa189fec81c26d04adba4bc0dbe09cd6e6fd9b7de04/nvidia_cusolver_cu12-11.7.3.90-py3-none-win_amd64.whl", hash = "sha256:4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34", size = 256720438, upload-time = "2025-03-07T01:54:39.898Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, + { url = "https://files.pythonhosted.org/packages/02/b0/b043d6f3480f102f885cf87fc3ffd3edcb5e23b855025a50e2ef4d059185/nvidia_cusparse-12.6.3.3-py3-none-win_amd64.whl", hash = "sha256:cbcf42feb737bd7ec15b4c0a63e62351886bd3f975027b8815d7f720a2b5ea79", size = 143783033, upload-time = "2025-09-04T08:42:12.391Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc", size = 288117129, upload-time = "2025-03-07T01:47:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/62/07/f3b2ad63f8e3d257a599f422ae34eb565e70c41031aecefa3d18b62cabd1/nvidia_cusparse_cu12-12.5.8.93-py3-none-win_amd64.whl", hash = "sha256:9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd", size = 284937404, upload-time = "2025-03-07T01:55:07.742Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/b9/598f6ff36faaece4b3c50d26f50e38661499ff34346f00e057760b35cc9d/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5", size = 283835557, upload-time = "2025-02-26T00:16:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d8/a6b0d0d0c2435e9310f3e2bb0d9c9dd4c33daef86aa5f30b3681defd37ea/nvidia_cusparselt_cu12-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075", size = 271020911, upload-time = "2025-02-26T00:14:47.204Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/31/83/f3647ce26916c94a6ca4ff1810623e2c405cff2dea6e78d29516b2514df9/nvidia_cusparselt_cu13-0.8.1-py3-none-win_amd64.whl", hash = "sha256:dccbd362f91a7b9024d1f55ee9f548ac065027ff15d8c8b0db889ab3a8f31215", size = 156885108, upload-time = "2025-09-05T18:51:35.958Z" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "13.610.43" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/b5/a8fbc356f768fa5c9cfd646668fd7d34bf55bdd1c6e20754642a64d930d4/nvidia_ml_py-13.610.43.tar.gz", hash = "sha256:65437eb73d68d0c62c931ca4d45038472faff03bd0b8729abba4b899f70d60f2", size = 52109, upload-time = "2026-06-01T18:54:08.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/45/caa600acfab94560807a20a64b5830d2cd3c3202b7f1328644d70b7d6bd8/nvidia_ml_py-13.610.43-py3-none-any.whl", hash = "sha256:f13c72698edef492f985cc225f14faafe68ae065a2e407f45bdf6f4b9b43fde8", size = 53163, upload-time = "2026-06-01T18:54:07.704Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/ec9c05a108095828dfc58840978c627b3c313fdf2a567c6de9ffbbb46901/nvidia_nvjitlink-13.3.33-py3-none-win_amd64.whl", hash = "sha256:4297ee49639b4f2e07255a1d69b3acc7ab2d011bb892b403e91ac98368962e3b", size = 37766359, upload-time = "2026-05-26T17:11:28.96Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a2/8cee5da30d13430e87bf99bb33455d2724d0a4a9cb5d7926d80ccb96d008/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7", size = 38386204, upload-time = "2025-03-07T01:49:43.612Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d7/34f02dad2e30c31b10a51f6b04e025e5dd60e5f936af9045a9b858a05383/nvidia_nvjitlink_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f", size = 268553710, upload-time = "2025-03-07T01:56:24.13Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/03aa43cc9bd3ad91553a88b5f6fb25ed6a3752ae86ce2180221962bc2aa5/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15", size = 138936938, upload-time = "2025-09-06T00:32:05.589Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/d2/50/0e2220f8620a177de994211186ffc5bfa9f2ce1e1282797f8f90096f9f88/nvidia_nvtx-13.0.85-py3-none-win_amd64.whl", hash = "sha256:d66ea44254dd3c6eacc300047af6e1288d2269dd072b417e0adffbf479e18519", size = 137066, upload-time = "2025-09-04T08:39:25.649Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/c0/1b303feea90d296f6176f32a2a70b5ef230f9bdeb3a72bddb0dc922dc137/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615", size = 91161, upload-time = "2025-03-07T01:42:23.922Z" }, + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/9f/99/4c9c0c329bf9fc125008c3b54c7c94c0023518d06fc025ae36431375e1fe/nvidia_nvtx_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e", size = 56492, upload-time = "2025-03-07T01:52:24.69Z" }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749, upload-time = "2026-07-02T06:59:53.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443, upload-time = "2026-07-02T05:50:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755, upload-time = "2026-07-02T05:51:30.556Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064, upload-time = "2026-07-02T06:53:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711, upload-time = "2026-07-02T06:54:13.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576, upload-time = "2026-07-02T06:54:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032, upload-time = "2026-07-02T06:55:03.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734, upload-time = "2026-07-02T05:49:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345, upload-time = "2026-07-02T05:49:54.971Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/99/76b7c80252aa83c1af16393454aafd125a0287101afe8deb0a6821af0e30/opencv_python_headless-5.0.0.93.tar.gz", hash = "sha256:b82f9831daab90b725c7c1ee1b36cb5732c367096ac76d119e64e14eb70d5f3c", size = 81817738, upload-time = "2026-07-02T07:01:06.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7c/8c8097891c509d98cd128493835c95631c80be6a8f37ed9d25716c2e16f1/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:030ca5e0837a2963ab36ef896baa9767eb8d2b83353fb28af5a521e40dd8756f", size = 48322581, upload-time = "2026-07-02T05:50:34.207Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/eab2ad388c3cbab2a350c10c2ef19ce6bd099240afc31789032c996bab52/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:1e55af3abfb462eeeabe5c775f12bdb36216d8a93a3583d69e6bd6e1d6ba7d00", size = 34782894, upload-time = "2026-07-02T05:51:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/ec/78/afca939f40ffe2b2380bfa86f812b2f7d4acc5a27b27dc41b49cad7ce7b4/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10818d91510e05c04568ae12b5cd120779c70c01bf897b001a6221fe430df80f", size = 36521085, upload-time = "2026-07-02T06:55:24.429Z" }, + { url = "https://files.pythonhosted.org/packages/2b/97/8170e9819764c47e436c130d3ff6cfb73b58f923eae9d3a03d8982b04aec/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09a872a157c1376ab922a69bbf22f9a95bcc7b658a9d8b436a60212b02b2eeb4", size = 56563598, upload-time = "2026-07-02T06:55:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/1a28a7101e31801042b3098871a74b76c61581d328ef40774ff4edb53a56/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:840bd717c21e5c11cadadc022a823315ea417f961213d06b4df010e019eb16f4", size = 39648433, upload-time = "2026-07-02T06:56:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/9b/21/f6ef335f6e65724aa78b8d792b48d40a48c381715f1e62f5a5049e09d07e/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed709fdf9aa0bd1f2ed8549e71d19449b03a675bb581eb292285f6861953be37", size = 61204038, upload-time = "2026-07-02T06:56:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/b8756467ea991449a293797f6b3fa80fcfdd29598a0a60d1cd5715b96e61/opencv_python_headless-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:c6bcd96b185975ea240d22cfdb15a1f6d080cc95264cfbe2621f21bb144d89b9", size = 35411237, upload-time = "2026-07-02T05:50:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/b8/88/763b967f7efd7226b82c9fae16d560cba049b1f0c036647e65c610fd636e/opencv_python_headless-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e", size = 43825962, upload-time = "2026-07-02T05:50:09.627Z" }, +] + +[[package]] +name = "openml" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "liac-arff" }, + { name = "minio" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm" }, + { name = "xmltodict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/9b/729dc6377bbfdbf0828d5567a335670d4e7c2866065ca4593ab525a5809c/openml-0.15.1.tar.gz", hash = "sha256:58ae3840b6ea736bb6c69bcbb30d587b817f64db070dc691adb9e09b99018816", size = 146141, upload-time = "2025-01-25T10:56:28.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/f3/462c16d9e80177d22a036abd3df0f114bf311e566ff906adbb3d82790c20/openml-0.15.1-py3-none-any.whl", hash = "sha256:14d25afb7a3007a70da26b0d1f46cf93df6f5784c31dd76d6a415066c6051961", size = 160384, upload-time = "2025-01-25T10:56:24.84Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "optuna" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/aa/05f5e3f662cc96a4c478fc3446b8ed6359825a2b504ecb614a9ac84e4a4d/optuna-4.9.0.tar.gz", hash = "sha256:b322e5cbdf1655fb84c37646c4a7a1f391de1b47806bbe222e015825d0a82b87", size = 485834, upload-time = "2026-06-01T06:23:30.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/f3/e5fcd5d9b15771ed6dc10e3a7eeddc672e418f4f4c4653d216cc1d857e2d/optuna-4.9.0-py3-none-any.whl", hash = "sha256:f52f3be6148654850c92a5860d398fd88ec6b2c84ab68d9c3d07dcff02e7afee", size = 425553, upload-time = "2026-06-01T06:23:28.804Z" }, +] + +[[package]] +name = "oslo-concurrency" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "debtcollector", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fasteners", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-config", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-utils", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pbr", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/dd78b7309433a3086abb12d07326f3d7760e81142c45fc589636979f0c34/oslo_concurrency-7.5.0.tar.gz", hash = "sha256:091ce0a27c5f347e393cf4776176389fe23c0b863ff0539d121619d0ec691367", size = 62621, upload-time = "2026-05-18T09:30:28.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/5a/04a381ab5fac98aa87c662fe9c693c8dd47e03d45c44821d70f2df060eaa/oslo_concurrency-7.5.0-py3-none-any.whl", hash = "sha256:11dfa769f330de64cad0aca2736f75239265e571b5e17edcb23f08f29b917aef", size = 48574, upload-time = "2026-05-18T09:30:27.51Z" }, +] + +[[package]] +name = "oslo-concurrency" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "debtcollector", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fasteners", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-config", version = "10.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", version = "6.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-utils", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pbr", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/d8/cd9a472611c8ce11c858195885c3e9383c054cca52e1f66ce287f6988d00/oslo_concurrency-7.6.1.tar.gz", hash = "sha256:8dcd3a6e931fc24d53832e887a124e3872e44e1af015cab47783bc67ad5b85e7", size = 64052, upload-time = "2026-07-16T08:58:12.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fd/c3d55e23b7de17704563364b40613b63a244fa9535bb5dc41f67067d8d54/oslo_concurrency-7.6.1-py3-none-any.whl", hash = "sha256:37cc117c5d0600542d3d88ce03efa96f53fc9324cdf3c056695674a680c488b2", size = 49130, upload-time = "2026-07-16T08:58:10.843Z" }, +] + +[[package]] +name = "oslo-config" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "netaddr", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyyaml", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rfc3986", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "stevedore", version = "5.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/a9/a1295eceb3a79ad46f32d145bade3119dc20636e2fda62adaba19c61195c/oslo_config-10.4.0.tar.gz", hash = "sha256:2ae3e02593474ecd7b64ec4eb11482adb4c928a78267bc820f5c3f80240b197a", size = 168943, upload-time = "2026-05-18T09:31:19.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/49/87743b93ea01f9cf667e78c1ceea1058f816ef733399f4e45469472cf2fe/oslo_config-10.4.0-py3-none-any.whl", hash = "sha256:0429c7b312114fb796005bdb718bba51e66de9c0814fb2cee22e754afbfe253b", size = 137504, upload-time = "2026-05-18T09:31:17.809Z" }, +] + +[[package]] +name = "oslo-config" +version = "10.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "netaddr", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", version = "6.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyyaml", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rfc3986", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "stevedore", version = "5.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/21/f4b1c021c49afedb7f90081a209370d7d9f172a06ea2dbb0cc9fd9ed5966/oslo_config-10.6.0.tar.gz", hash = "sha256:be3ff9da13be8f17220c6d87a1c6ad3a9d8641d2d41324e04b57f9eef5724c51", size = 175381, upload-time = "2026-07-16T11:16:28.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/22/551779c5d526156d1455f0f34a54b0c731cdef6108bb3fbfe2202498173d/oslo_config-10.6.0-py3-none-any.whl", hash = "sha256:5abc4db4ff3788a8f614731e8ab9b9756df9f9e936ceeb9aeca6c9a7c182d52e", size = 143461, upload-time = "2026-07-16T11:16:26.844Z" }, +] + +[[package]] +name = "oslo-i18n" +version = "6.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "pbr", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/26/85800d24c3aa7650bbd5fa0398aca78a84e8a8693f9c6a852148a196ddac/oslo_i18n-6.8.0.tar.gz", hash = "sha256:a0b4c64c1396869d7144dca60ad97c7eb028f78f61f91c7007531238051997df", size = 50114, upload-time = "2026-05-18T09:16:54.09Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/35f3022c3e80605c96a9caad2c850d0904e37c9a74b57982f79002f09f66/oslo_i18n-6.8.0-py3-none-any.whl", hash = "sha256:77a6729535ec5a49f72bc64d7795b673a88c1b9c753e0fca45d4e6b14e892bb9", size = 47809, upload-time = "2026-05-18T09:16:52.437Z" }, +] + +[[package]] +name = "oslo-i18n" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "pbr", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/69/72b03bb4d33f51a157c02d5297227bae48b9c359103856942b8774b608df/oslo_i18n-6.9.0.tar.gz", hash = "sha256:574bcf21873b185068bcec951de1ec093158ffdff05a8055fd18ddcb69f69e65", size = 50369, upload-time = "2026-07-10T13:44:34.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/3a/f9f9be0fd3b5c180b64f08daef70ca20fbbdccab47d0a21b65f0e4b5b8d1/oslo_i18n-6.9.0-py3-none-any.whl", hash = "sha256:354b6cd3ba1acf1f04231f690028fd5b58bd8b7cda7bd9859126bb97152c717b", size = 47847, upload-time = "2026-07-10T13:44:33.205Z" }, +] + +[[package]] +name = "oslo-utils" +version = "10.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "iso8601" }, + { name = "netaddr" }, + { name = "oslo-i18n", version = "6.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", version = "6.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pbr" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/16/8cb5305abd34606bd9a5ee1c6fbe5db97981d323c8454f1d872c1781dcc8/oslo_utils-10.1.1.tar.gz", hash = "sha256:c8ac3ee295303cc5776c4d8e1d4ef10078ece60ede4931177e4f07aca58f81ab", size = 159381, upload-time = "2026-06-09T13:13:27.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/b3/c7afc60b9392b335ce0ad121977f91df9706f47f2c518b27e4b3007e9afb/oslo_utils-10.1.1-py3-none-any.whl", hash = "sha256:dcc7aa6668aa84fc14a18a4064b389b2992e1ee793ba38dadac327dfba69dadd", size = 154564, upload-time = "2026-06-09T13:13:26.365Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pbr" +version = "7.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" }, +] + +[[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/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { 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 = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + +[[package]] +name = "plotly" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/07/795c79dbce40c39bece88e69d049babbd23ffa95b5d117f248db8ea03abb/plotly-6.9.0.tar.gz", hash = "sha256:967ad33e8c704fed051800d11d985eb206a9c795c14206b30a6f463ed9c67d0d", size = 6919903, upload-time = "2026-07-09T14:55:59.982Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/18/d8544811ab076f876c4892b3714f5b0dad335e1dc33aef826df431b8325d/plotly-6.9.0-py3-none-any.whl", hash = "sha256:36bebe2f1bb13884774fe61689c329071446f6ce4a8927fb1f0d6fb24f581236", size = 9909646, upload-time = "2026-07-09T14:55:55.421Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, + { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, + { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, + { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "proxy-tools" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978, upload-time = "2014-05-05T21:02:24.606Z" } + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pyarrow" +version = "25.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/f3/95428098d1fa7d04432fb750eed06b41304c2f6a5d3319985e64db2d9d41/pyarrow-25.0.0.tar.gz", hash = "sha256:d2d697008b5ec06d75952ef260c2e9a8a0f6ccfce24266c04c9c8ade927cb3b4", size = 1199181, upload-time = "2026-07-10T08:29:50.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/2a/eaa70e6d6ed430c2e90c0599e2831a41a50251879e44788ccdbc73115af1/pyarrow-25.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ce0ca222802087b9a8cb031a6468442cb6b67c290a45a601cac64753d34954d3", size = 35945551, upload-time = "2026-07-10T08:25:23.153Z" }, + { url = "https://files.pythonhosted.org/packages/df/e0/917086af6b246143012cdc8a7c886b018b53204f3d69fc5f9be5857a8b80/pyarrow-25.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7d6da02ffc7a3a9bda3b7ded4cc2a27ff73969ab37153f3afd46bbbc1ba4f0f7", size = 37636698, upload-time = "2026-07-10T08:25:28.031Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/c87829f92503f84993721791c942f3d9aa81044de51a8cfb1da5810e5345/pyarrow-25.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:dbf9fa5d4bde73b1cc16377dcaaa010f971e6fa7f5083f5d44f34b50bc1d74af", size = 46858364, upload-time = "2026-07-10T08:25:34.527Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ba/2030d454c2747e26cce23e4a0338067ee0830a155b7894da04caa96783a5/pyarrow-25.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b72d943ff4e10fec8d48aedb23322d8f6ea8bc2d698b81db37e73730f69e4862", size = 50056398, upload-time = "2026-07-10T08:25:40.785Z" }, + { url = "https://files.pythonhosted.org/packages/78/ce/ba7a5ce7bf0cfc372ec48203a34ece42f73aa2f3231706f61c55e105ecd0/pyarrow-25.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5fb2d837960f1df7f679ff9f1a55065e306347d379e0768cebf14781254d6194", size = 49958146, upload-time = "2026-07-10T08:25:46.98Z" }, + { url = "https://files.pythonhosted.org/packages/75/eb/c34a29fb7a70dca2f903c7d85a928928ef55af20cd56e99de6b4c0d897bc/pyarrow-25.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:add690feafa0953c443cdba9e9e87f5eaa198f1ea2e43a3b146ea83f202262d0", size = 53096264, upload-time = "2026-07-10T08:25:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/36/f9/35b1f83a0727d84951588e4034aca2feb76dfb45b0725918c0037b0a48f7/pyarrow-25.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:d293e9959b29a24c82d936d04ab2b7fd8b8d334030de2e56a99aba94f008ad7a", size = 27840572, upload-time = "2026-07-10T08:25:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/a7/98/ae2b5acf9876dbeffa6f320776242c52caab062df55c8ac5501ed2679e74/pyarrow-25.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2e3b6544e26e393fe2cd530f523e36c1c8d3c345bbbb60cca3fd866be8322517", size = 35939080, upload-time = "2026-07-10T08:26:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/3de2a968edbd496c86cb8b932cdbee2d4b08c4a28e9884a15e5c705a646b/pyarrow-25.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:b724d127783b4c19f088fcdfc844cbc318809246a30307bcabd5ed02045e890e", size = 37633420, upload-time = "2026-07-10T08:26:10.354Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/8399243a4ce080426ec37db18d5e29148b7ec960a8a8c7f9059a7bf6ef0a/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:244f98a595f70fa4fd35faa7508c4ae67e14a173397a4b3b49d2b3c360fb0062", size = 46861050, upload-time = "2026-07-10T08:26:16.397Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/72d704b02bc5fc6d06954d76a0208c1e79cad3ab370f6d6a91ffe5078870/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0222f0071d13313962a88d21bf28b80d355ac39d81bfa6ff3fe00eeaf748e4be", size = 50056458, upload-time = "2026-07-10T08:26:23.271Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/3c31a60b6403d63cad2e0f829096f5fc5763a129ead4207a5d4690b96448/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b58726f118c079f9d4ed7e904975d4f15fd69d0741ba511a4e2dcaa4ef16354f", size = 49957793, upload-time = "2026-07-10T08:26:30.232Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/8f8a019061f9863a831915329264372a87ed25eaf9109ce56eb0e84012c5/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:38a2c887cb3883e241b70201688db34133b6dfadd04f03c8f9213df53770c18e", size = 53100544, upload-time = "2026-07-10T08:26:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e2/738071e95c5ddad7b3dfc12f569ffa992db89d7d7b4a95258fd184191249/pyarrow-25.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:161649d60a7a46c613a19fd795763ea8a88c36ba997dd99d9bc66e6794ee36e8", size = 27848311, upload-time = "2026-07-10T08:26:41.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/fdd3a4377807b7dcabe2d4b5aa99dbbc98e2e5df3f1ca4e7f0aec492d987/pyarrow-25.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:149730a3d1f0fb59d663a0b8aa210adfd9c17c27cd94a0d143e60daea8320d4e", size = 35850884, upload-time = "2026-07-10T08:26:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/bf/71/9f053177a7709b8c90abb00a2375b916286f9f0d6cfb21a5cadd4ef811e8/pyarrow-25.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:0721332c30fdd453fdd1fc203b2ac1f4c9db5aea28fa38d41f2574c4b068b9ec", size = 37616197, upload-time = "2026-07-10T08:26:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/95/1a/22bfb6597dcdc861fa83c39c06e1457cb56f698940eff42fbb25de30e8e5/pyarrow-25.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fa1482b3da10cac2d4db6e26b81da543e237616af2ef6d466018b31ca586496f", size = 46841966, upload-time = "2026-07-10T08:27:07.685Z" }, + { url = "https://files.pythonhosted.org/packages/55/0e/cd705c042bc4fe7022478db577fcab4abdcfabb9bc37ab7a75556b3fcb2b/pyarrow-25.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5d1dbf24e151042f2fa3c129563f65d66674128868496fb008c4272b16bdf778", size = 50088993, upload-time = "2026-07-10T08:27:14.268Z" }, + { url = "https://files.pythonhosted.org/packages/98/ee/d822e1ee31fe31ec5d057210e0605c950b975dcd8d9a332976cc859a9df8/pyarrow-25.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:20887a762dd61dcc530f93a140840ab1f6aa7836b33270e42d627ab3cf11e537", size = 49941005, upload-time = "2026-07-10T08:27:21.274Z" }, + { url = "https://files.pythonhosted.org/packages/33/1b/207a90cc64619a095eb75a263ae069735f2810056d43c667befd573ec083/pyarrow-25.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58d1ab556b0cea1c93fdb799b24ad58adb2f2a2788dbce782a94f64ae1a5cc9b", size = 53112355, upload-time = "2026-07-10T08:27:27.911Z" }, + { url = "https://files.pythonhosted.org/packages/7e/fe/81d1e5f8beed15c01e98649d5c6e2167b67fd395884a2488f18bf1cf0dba/pyarrow-25.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:3f356afe61186395c861d5cd63dc21ff7d5fa335012a4668d979257df7fea0f5", size = 27945954, upload-time = "2026-07-10T08:27:32.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c8/098ce17d778fd9d29e40bb8c5f19a40cc90c3f0b46c9057b0d7993f42f54/pyarrow-25.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:8831a3ba52fa7cdb78d368d968b1dcd06171e6dff5461e16d90de91d371e47bc", size = 35844549, upload-time = "2026-07-10T08:27:37.956Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/24c28877219abf6263d909b1592c97ff82c59f13a59acbed11fc87c0654f/pyarrow-25.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:5f4bacb60f91dd2fca6c52f1b9a0012cd090e0294f1f781dc1881a247a352f8e", size = 37610397, upload-time = "2026-07-10T08:27:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/53/55/6d1d5f5aff317ec5de9421594679ed51ed828fe7e2ce209327f819d801e4/pyarrow-25.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:59516c822d5fd8e544aaa0dfe72f36fed5d4c24ea8390aab1bcd31d7e959c6be", size = 46841701, upload-time = "2026-07-10T08:27:49.741Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5d/f790fb6965ab54c9da0dda7856abc75fd0d7648d865f8d603c111d203a64/pyarrow-25.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f9dbd83e91c239a1f5ee7ce13f108b5f6c0efbe40a4375260d8f08b43ad05e9", size = 50090118, upload-time = "2026-07-10T08:27:56.051Z" }, + { url = "https://files.pythonhosted.org/packages/0c/8c/faf025357ebf31bc96777f234277aa31e2aeca6dd4ecaa391f29085473c2/pyarrow-25.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18dcc8cc50b5e72eae6fcbfc6c8776c21a007176b27a3cdec5c2f5bcf126708d", size = 49945559, upload-time = "2026-07-10T08:28:01.927Z" }, + { url = "https://files.pythonhosted.org/packages/07/a1/bd051871708ea99a5e0fc711926c26c6f2c6d0130c7aaac8093e34998af6/pyarrow-25.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4ec1895a87aa834c3b99b7a1e758747eb8bb57f922b32c0e0fa04afb8d6998b1", size = 53114238, upload-time = "2026-07-10T08:28:08.594Z" }, + { url = "https://files.pythonhosted.org/packages/7c/31/737f0c3cffcd6af647849477d1dd68045deac2e3963c3f9f211bedc48540/pyarrow-25.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:77c8d1ae46a44b4006e8db1cc977bbcc6ce4873c92f74137d68e45503b97fb18", size = 27861162, upload-time = "2026-07-10T08:28:12.975Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/581ccbcdb3d897eb2893328d68db3d52eca373bf2a7e964d0a6276b8e85b/pyarrow-25.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:72132b9a8a0a1840197794d4dea26080069b6b0981c116bc078762dc9691b21b", size = 35878945, upload-time = "2026-07-10T08:28:18.222Z" }, + { url = "https://files.pythonhosted.org/packages/64/d1/ccb01db7329ea0411ef4fbd9b62a04d3268b36777d4e758d5e39b91ddeab/pyarrow-25.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e009ef945e498dca2f050ea10d2e9764cb44017254826fc4574fdb8d2530173b", size = 37630854, upload-time = "2026-07-10T08:28:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/af/9f/2d81ba89d1e4198d0cb25fe7529de936830fdaec0db926bb52a1ef7080d4/pyarrow-25.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:f57a39dbcb416345401c2e77a4373669b45fd111a1768e6cf267a7a0607ff0ec", size = 46905617, upload-time = "2026-07-10T08:28:29.376Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/0ed312ec800fb536f93783215126cee4b8977dcfeccba6f0f44df0cc87d7/pyarrow-25.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:447df764beb07c544f0178a5f6b70ef44b9ecf382b3cdfad4c2d7867353c3887", size = 50119765, upload-time = "2026-07-10T08:28:35.826Z" }, + { url = "https://files.pythonhosted.org/packages/ca/88/cab5063ba0c4d46a9f6b4b7eb1c9029dc0302d65cd5ab3510c949a386568/pyarrow-25.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac5dfeee59f9ceb4d45ba76e83b026c38c24334135bb329d8274baa49cec3c62", size = 50027563, upload-time = "2026-07-10T08:28:43.848Z" }, + { url = "https://files.pythonhosted.org/packages/7b/fb/4d24f1b7fe2e042dc4ef315ef75e4e702d8e46fe10c37e63caff00502b03/pyarrow-25.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0f100dacf2c0f400601664a79d1a907ced4740514bb2b00917341038e2ce76f", size = 53162437, upload-time = "2026-07-10T08:28:52.819Z" }, + { url = "https://files.pythonhosted.org/packages/fa/65/da20806de93ca6ee91e72cb6a9b08b3ac890b46efc8d94a7326c651c4c81/pyarrow-25.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:2e093efbecb5317372f819228fa4b4e6157eee48d3f0a7b0303705ebf81a7104", size = 28613262, upload-time = "2026-07-10T08:29:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/86/9f/c632afb1d3ef4a7814cee236718235f3a47eac46e97eb87df40f550b6b48/pyarrow-25.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:26be35b80780d2d21f4bae3d568b1666337c3a89722cc1794c956a77017cb24e", size = 36120702, upload-time = "2026-07-10T08:28:59.577Z" }, + { url = "https://files.pythonhosted.org/packages/36/0a/093d53a0e72ad06e45d6443e00651bbc2d21af4211295086cbf4d873d3b9/pyarrow-25.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:6f4812bfbf11ca7d8faf59eb8fff8bf4dd25ce3a38b62baa010cc17a0926d1b2", size = 37750674, upload-time = "2026-07-10T08:29:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/b37fc31a69cff4bdfb8842683def5612f551b93fff6f44375e4a4a6a5535/pyarrow-25.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b8af8ceedf0c9c160fd2b63440f2d205b9404db85866c1217bfea601de7cfb50", size = 46912304, upload-time = "2026-07-10T08:29:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/35/5cae19ba72493e5598022468b56f6a5571f399f485bf412f157356476caa/pyarrow-25.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c70a5fd9a82bd1a702fd482bdc62d38dcb672fb2b449b1d7c0d7d1f4be7b7bfe", size = 50073652, upload-time = "2026-07-10T08:29:22.467Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a5/ddd508424bdfd5e6945765e9e2ffc687e2f6115972badc8ecf423076c407/pyarrow-25.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0490a7f8b38ffe11cc26526b50c65d111cb54ddac3717cec781806793f1244dc", size = 50058654, upload-time = "2026-07-10T08:29:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a4/324d0db203ff5eebe8694ec2d6ec5a23f9aaa5d02e5b8c692914c518c33c/pyarrow-25.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e83916bbcf380866b4e14255850b33323ff678dc9758411d0409cdd2523880b0", size = 53140153, upload-time = "2026-07-10T08:29:36.041Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8d/d236e9c82fe315f9128885c8be3ec719f41965a1eb6b6f4b42470904cd41/pyarrow-25.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:13240f0d3dc5932ccd0bfa90cd76d835680b9d94a7661c635df4b703d40ce849", size = 28743657, upload-time = "2026-07-10T08:29:42.742Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pymc" +version = "5.23.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "arviz", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cachetools", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cloudpickle", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pytensor", version = "2.31.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rich", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/25/ec325ae8723d90c09924c1a18c0922fcf8099d82fd2bab9e937bb8ca8270/pymc-5.23.0.tar.gz", hash = "sha256:23fc95234d4364b5a78cc6311531447099e96d01c8d1c628bff42565d4450bbf", size = 477350, upload-time = "2025-05-28T06:02:15.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/8e/cdacf953ba5f06d717f80a5e9bcf033a5bfe3ef1b67d406b0446253920f5/pymc-5.23.0-py3-none-any.whl", hash = "sha256:4c157d76cbdac88fff629652a8aac527f66ca568db706a4008ba35fe4c88d79c", size = 519595, upload-time = "2025-05-28T06:02:13.288Z" }, +] + +[[package]] +name = "pymc" +version = "5.28.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "arviz", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cachetools", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cloudpickle", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pytensor", version = "2.38.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rich", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/35/07a37ba2b3a7d5b950537ba0f2178f9dd5ddd6d41cac15b9b5aaff5e51cb/pymc-5.28.5.tar.gz", hash = "sha256:cce66ef38563bbb5e96fafbc617eb49e3332e24a60b9d335dfc20e8e31243582", size = 508142, upload-time = "2026-05-01T14:36:35.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/f9/919387e73bafb9c1130d7051c7db0eddc0d74b8d4a9d13907a43cc065d4e/pymc-5.28.5-py3-none-any.whl", hash = "sha256:29f44aa3edd727b29eb0a555eec0d9def4775a5d5c849eee31239384a5feaec2", size = 562936, upload-time = "2026-05-01T14:36:33.091Z" }, +] + +[[package]] +name = "pymc-bart" +version = "0.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "arviz", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pymc", version = "5.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f1/0031d51c6ad12c2d178d4394866077967552286fe1ae48569ef51bdc706e/pymc_bart-0.9.2.tar.gz", hash = "sha256:7509b68c2a74cec5c11691b25e34e02cc8909f2a9069c5e36680ae86260e3693", size = 41306, upload-time = "2025-06-12T12:40:36.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/68/9317f580d73718fe03823a8221d71f093266e4fb284423e8d0c8ceb52653/pymc_bart-0.9.2-py3-none-any.whl", hash = "sha256:dacda5dc22a229c973aca13bc007318d016382373d191842e0ebc4d6cc6dc2f2", size = 31667, upload-time = "2025-06-12T12:40:35.414Z" }, +] + +[[package]] +name = "pymc-bart" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "arviz-stats", version = "0.8.0", source = { registry = "https://pypi.org/simple" }, extra = ["xarray"], marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "arviz-stats", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, extra = ["xarray"], marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pymc", version = "5.28.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/d1/0de1909cc35f7b5df98fa5bd93585f1489a1bb59161d10e023acfa7c6c59/pymc_bart-0.11.0.tar.gz", hash = "sha256:fc107a29f12c7a4345fca33ae3fda5208da4788e0f5e776a37a71cb871097d95", size = 43418, upload-time = "2025-10-21T10:27:59.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/1a/c2c8f28f9522d4e08c402949b350e11249be492663f7e95ac209d1979dd8/pymc_bart-0.11.0-py3-none-any.whl", hash = "sha256:c07ec5cf854a39c6f7328998d01d96da9ba956fb27020f691f90a1dc65c1855f", size = 32758, upload-time = "2025-10-21T10:27:58.738Z" }, +] + +[[package]] +name = "pyobjc-core" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/b1/729f7458a63758bd21716648a8abcd9a0c8f2d2e9897763c8a1a1c7fd31b/pyobjc_core-12.2.1.tar.gz", hash = "sha256:7a7b9b018402342cf32bf1956366896350fbe5c0478cb3ef59778f77abed7f07", size = 1063383, upload-time = "2026-06-19T16:19:39.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/62/80fe6b6bea9e9e7abe2e7a91bfd22115219b2263e435d2da09cf480b6f49/pyobjc_core-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa5c889961d79d7704f17eeb27f80ee791335819c1bb14babeff7b9ea665b5f0", size = 6486390, upload-time = "2026-06-19T13:29:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/92/87/16564ef5e4568ee0edd9e712d8111dc8b67621d6bb6ff430646ee2d637dd/pyobjc_core-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:24b76a63caf0b5369d4a377c7c0438cd70df81539057af3db839bfaa3579e04a", size = 6484662, upload-time = "2026-06-19T16:04:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/8c/88/300ad283bed0c971c52dcac6f70113e138169d4ce6d856ddd03d16081e51/pyobjc_core-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a64232bb27ed101d4adc7d42b0e64a6d3331aac7bee7861c037a6777a163f10b", size = 6433347, upload-time = "2026-06-19T16:04:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/b9b0ddffae66996b8779f1f7958adc9f21c13a0448cd3be8d7fe589b5b0f/pyobjc_core-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af101222762665a4125157906cb4b23f5d5a63d3851d5e0504f72a1eaaa2cfd2", size = 6436004, upload-time = "2026-06-19T16:04:53.257Z" }, + { url = "https://files.pythonhosted.org/packages/8f/26/bd309ede07784c6e5fac4b440c90a5f72a66da7859ed303a9392fe8a5f3f/pyobjc_core-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:efe465e3ecc6fc73f7c7622620345d134a8d34564ab1c29d8247e45f4ed55071", size = 6687044, upload-time = "2026-06-19T16:04:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8a/cfa4f56939d554dbb342ec6e5226a441e2f552bc2002a0ddf7705bb11bef/pyobjc_core-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2b8fc0531c27277325e113ac00b8a72a82e6145f0a88175b9425d8de814ff69a", size = 6429289, upload-time = "2026-06-19T16:05:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/74/446c89bc18103aaa4a00d1fb85ff8acace9a0dc3f362d9678ebf7571e275/pyobjc_core-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bef500f979e22d54f9da3aaebf6a48f873234b324858bd69256055a318955c7", size = 6690181, upload-time = "2026-06-19T16:05:06.201Z" }, + { url = "https://files.pythonhosted.org/packages/99/c7/0121ee4c616af07ad2de8cd1a286f6978dc9a227eb58b7c2e875cb68a1df/pyobjc_core-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:047c226eeb58a2993ace5e8904e71cc9426ee20d064c617f8fbf32717d37093e", size = 6487078, upload-time = "2026-06-19T16:05:10.093Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a8/cb9fcc150f97d0bf22a2028f88b24cc35949beb1bcc7b8bc5c17d4401677/pyobjc_core-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1188613805336270279570467e4455b74cb6c0f60913ac74c917ee1c37cfaecb", size = 6733064, upload-time = "2026-06-19T16:05:14.313Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/34/fbe38a204643aa4e1b91391cdce07a34da565a69171ebcad08de7438a556/pyobjc_framework_cocoa-12.2.1.tar.gz", hash = "sha256:b94b37fe5730e5ae1fb0052912cd174e6ec329b0bfba4a012ae5db1014b5864b", size = 3125751, upload-time = "2026-06-19T16:20:05.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/c5/f6a5458cc5a598baa7a79ffe86248560c631f5a86f4a3c678fb6a815e78b/pyobjc_framework_cocoa-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05443c1494532779cccff83e9792fdd6f0a4800cac56620132bf28e0bf966e9d", size = 387303, upload-time = "2026-06-19T16:07:35.831Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d6/dc66ea8519a0475efbccf73f82cc28066339bb300a27f5e1bf91ab1d7002/pyobjc_framework_cocoa-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc6da84f4fc62cc25463bbb85e77a57b8d5ac6caf9a60702daf2edb601332f15", size = 387298, upload-time = "2026-06-19T16:07:37.412Z" }, + { url = "https://files.pythonhosted.org/packages/f7/cf/1b3b32b2f28f66cc053c3438ef4e6df36a1591945bf05e7399da18d74553/pyobjc_framework_cocoa-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:28b9b8bab1c36efb94744786918752d0c1842f5fbb67e7d5ca97b5f736512080", size = 388113, upload-time = "2026-06-19T16:07:38.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/46/68e8e4d926a2f70fed0437047bc3f9fe08af8fe620d94d80656ebc3cfa9b/pyobjc_framework_cocoa-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3b74a78fa7803e547b32e5e8ec1b49987b52fe318383e793bc6cd49b80efbd9f", size = 388183, upload-time = "2026-06-19T16:07:40.483Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f3/dfc9af4c9eb2e5389c860ad5ef252be9fe456db09f39d537555dc5057aa1/pyobjc_framework_cocoa-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dc2eaca2f13c7bcd8e41e51a372e47825dea9dd3126108760eed7ba883d2945c", size = 392275, upload-time = "2026-06-19T16:07:42.078Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c8/b90baa8f3592eded79b4be98fb59d2b8dc16b62361e34292bd95806ebd9f/pyobjc_framework_cocoa-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b386c324d64ae565c1f6b7dfb77be68f640a1c7c23caa6966ab661131f519561", size = 388357, upload-time = "2026-06-19T16:07:43.364Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/64a94651b9294702d55e748d94de30e25bc59d0784526be7643f4467eccd/pyobjc_framework_cocoa-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a6c584e2af0813cb2f6103b184e632665a26f58c1bd5b08ffd6e95a19c617f7b", size = 392404, upload-time = "2026-06-19T16:07:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/26e8a7bf1f5e8caa38b7f80d486296f9fd3c97e71ad7e5444ef22e802758/pyobjc_framework_cocoa-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b6023657b8d6cc049a21bd6b4752425f2f53c42f9f0b02d64c7608cc484bf103", size = 388589, upload-time = "2026-06-19T16:07:46.276Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eedf743a303ea742b8e082afe3613fb4d6618bc1a48cf2568b004ce906f7/pyobjc_framework_cocoa-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:c685ccd8e266a07cf912a2c5a13b1f2eff2a868a1aff163b4801b4687bd425e1", size = 392691, upload-time = "2026-06-19T16:07:47.477Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/f6/2a8b84dbf1fe7c04dd96ea73d991678d4e09a909f51971ecc51629bb2ab4/pyobjc_framework_quartz-12.2.1.tar.gz", hash = "sha256:b3b8b6f71e66147f8ff9e6213864cc8527e3a0b1ee90835b93ce221f4802d9b0", size = 3215521, upload-time = "2026-06-19T16:21:30.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/8f/7b2abbade50ed918e59c3dfe14aece0f0f8a4dc43a3884fd4f2508c7787a/pyobjc_framework_quartz-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c8fed57ac8a1927e4fe4f48ab4042cbc1087d881743182b6f3f2e6e227a9209f", size = 218013, upload-time = "2026-06-19T16:16:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/527d1ff856e2f2446b5887be01989cc08f9adaf3de7d4eb13d07826c362f/pyobjc_framework_quartz-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60f29408b4f9ed5391a29c6b63e2aa56ddfb8b66b3fb47962930427981e14462", size = 217998, upload-time = "2026-06-19T16:16:02.978Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/d7c7b3134cdbd1a487f3f77b5be125d87a6c9e7d9411035739d99335cc0c/pyobjc_framework_quartz-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:de9c8cca7e95290c8d540466af11c7cdfe3a5458e6f56c34006d5b45243f9ed9", size = 219000, upload-time = "2026-06-19T16:16:04.29Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4b/861f91a1565d3189ee899e177b915551fb9a7e2ca25414025a8974f04e74/pyobjc_framework_quartz-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54c9bc7f507192691841ee4eba5bf36990b259df83ac728efed2d7ea1cd021e4", size = 219403, upload-time = "2026-06-19T16:16:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/b27010d2f288737f627f74be6d5549f49c841542365c84b9a3011fe39ce7/pyobjc_framework_quartz-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bfc0d2badd819823d21df8069dcf9544ce360ed747a8895c51bdb25d8d125f45", size = 224458, upload-time = "2026-06-19T16:16:07.252Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/85ffd9d433989205d572a50d625c63b29c05e0c5235a725f15ae1023672c/pyobjc_framework_quartz-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ceb56939c337b36d9d81185ade31f77dc52c85cf79bb16e53e9b32f54b6bb3f5", size = 219769, upload-time = "2026-06-19T16:16:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d6/b917e4b63d72ea84a27121076f3033f23f6497c0e6ce8d304766c899897f/pyobjc_framework_quartz-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8105c98b798f2bf81c05c54bddeeadbf62f0b5dfec13bd6e719dd2cdf7e1cddf", size = 224717, upload-time = "2026-06-19T16:16:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/04/e2/f3c1ed3228f7430ef5ade23db6f1fcbae99290f177ce5653348fd9e05f4d/pyobjc_framework_quartz-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:bbc214f1a216b5d3651bc832d0ac4589f029f3f37cd6cbb370aac12a7c77942c", size = 219825, upload-time = "2026-06-19T16:16:11.433Z" }, + { url = "https://files.pythonhosted.org/packages/66/2a/2c99a5ad2fe0a11600ea123b8e9a08ff138fcb2ad1e13e376f4bd4aa1d96/pyobjc_framework_quartz-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ca61624a0b0e6286d8a0f97f47eb9011e4e81e9a339db436d48af527e7065bb1", size = 224770, upload-time = "2026-06-19T16:16:13.035Z" }, +] + +[[package]] +name = "pyobjc-framework-security" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/b8/4267b802d8dba6de468e7d0765b05cc4e146fa376ed9f55e0b6461016bef/pyobjc_framework_security-12.2.1.tar.gz", hash = "sha256:d7831b1537f4346892e7f2f0e2b09d79bee98919b0767f4061278d0e03028f2d", size = 181065, upload-time = "2026-06-19T16:21:40.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/6d/5de5ba240d815ec0292db63f733b485f59ee7fd683375786c7b940ba7984/pyobjc_framework_security-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b490fb0d46275f165b1214785fec732bc7b8e06c99b6ba045382404d44400d9c", size = 41303, upload-time = "2026-06-19T16:17:15.804Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/f2ff946edfaf16b4ce5e31afac5e519f83705c0f4842fd25134ecb8f2f4a/pyobjc_framework_security-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce461296b003b2ba17c8b65f6339f9d2fd5dcfa2b3b52ddc0a696334cc8974c5", size = 41306, upload-time = "2026-06-19T16:17:16.816Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/2719bc4062e6c27083191fd20e365ae02d0bf1c22f4d1a88211e3d96b369/pyobjc_framework_security-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76ff6e44e62d3e15651540493879bf16687d862c4f10f3cadade757811c8b8d0", size = 41300, upload-time = "2026-06-19T16:17:17.702Z" }, + { url = "https://files.pythonhosted.org/packages/15/90/dccd4cd6877ef208957dc1f3675287d8614a4dcd2a3ee0a5e56f5fb5a1ba/pyobjc_framework_security-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:990013baba29d6f985d8950b23701129b2597b3d16f628b785fe97596d8a8de3", size = 41299, upload-time = "2026-06-19T16:17:18.511Z" }, + { url = "https://files.pythonhosted.org/packages/ce/af/f9e8040e0c3ef6a50392a46ad1df482a666aa615180d40730b00282ff81f/pyobjc_framework_security-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:066a3e5e9d368e7a6ba8dd52be2077a634ef12a54fbfcc78b3b8154a8f988a1d", size = 42179, upload-time = "2026-06-19T16:17:19.48Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3c/76e2a8bb8d5fe48f0e8e25c6abec1609f3667cc39935017badfe9e9603f2/pyobjc_framework_security-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5319ae49b8874363ab51c6ff4d85d4ea0cfa6d836fe0306e901ba9ae560b880d", size = 41370, upload-time = "2026-06-19T16:17:20.501Z" }, + { url = "https://files.pythonhosted.org/packages/14/6e/7120956e9833b2c70757eec1f65f57c191e00662cf74c4545d88315643fa/pyobjc_framework_security-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:21618431e0dbfbd3d4029445e3118af88e5d7e52ddecf9a2d17c759c51628d85", size = 42926, upload-time = "2026-06-19T16:17:21.425Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/0bafc557523e5755f74dd5363386a1e9b03f611e2e36df0737a508cd5ab4/pyobjc_framework_security-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:fa192e9df479375e6242adcadb9a44f32907dd7fe1207608710cd3af65fe3c84", size = 41376, upload-time = "2026-06-19T16:17:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/47/33/33d266117e46fef148caa4f986b3d896cb9bfd76bef48bd761cb60c758ee/pyobjc_framework_security-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:07cd044a7996f9a897040c49055fa3bdf565acac4a25b834a72e60602376146d", size = 42944, upload-time = "2026-06-19T16:17:23.371Z" }, +] + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/a1/108fa1e5a3dd8aff626f98fb97de370323b290404b04ffa2ef9420665ed3/pyobjc_framework_uniformtypeidentifiers-12.2.1.tar.gz", hash = "sha256:1fb89d13aa3c2df8e6d6536f6df3493fe5a6caefd2a5adebf17c5af3b29ed4a2", size = 20679, upload-time = "2026-06-19T16:21:55.739Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/44/18a7b3c3b4f9f6784fddf64ed5a2c148577d0300705a50e8ab81da8fc71d/pyobjc_framework_uniformtypeidentifiers-12.2.1-py2.py3-none-any.whl", hash = "sha256:ea08413ad895a7dfea13670e26548bcf5b00154084cdfb5d8f96603320e77cf3", size = 5042, upload-time = "2026-06-19T16:18:54.085Z" }, +] + +[[package]] +name = "pyobjc-framework-webkit" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or extra != 'extra-6-dashai-cpu' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/d2/b230c594f70ecb970b4cef67bae2648d1bfa5b381e9b7e3710bf24ec8887/pyobjc_framework_webkit-12.2.1.tar.gz", hash = "sha256:a56acae55b50d549b20dff2921ad1099add8fbc377d0de09ddc2ba50957f7def", size = 332374, upload-time = "2026-06-19T16:22:01.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/34/ec01564ac00165736c76481ea31a0a644c2b9fafac39e7fca827da2c4a34/pyobjc_framework_webkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad59030aeb9bb11d28a939760cb64b93108b6a8508c6001cf87789fd7a9312bd", size = 50260, upload-time = "2026-06-19T16:19:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/2ab99d3975dd4624dd943e5a7c8d37e40258d3c9fcf4f26baf09a24e6c9b/pyobjc_framework_webkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af5c4ccdf03845adac082823a3b4341b5b2fe62d2d664550afa705b5286a06fc", size = 50264, upload-time = "2026-06-19T16:19:30.607Z" }, + { url = "https://files.pythonhosted.org/packages/84/47/7a2099eb2e062c6230a9440f1795cf34056ca5e16ef25c8aad7c059b8734/pyobjc_framework_webkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e04dcc08cdc59380113ea1232af75a0a04c2426418ebe967b4c0045c973f776", size = 50372, upload-time = "2026-06-19T16:19:31.581Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a4/202ec288808011d3f459d000d593e88b1118f2d1d5a4dfaaf5232f2c2ac2/pyobjc_framework_webkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:23bee8bf7077f91da4e3ae54a00c7f5e4414319e15f98be8584dbd67c4043fae", size = 50387, upload-time = "2026-06-19T16:19:32.522Z" }, + { url = "https://files.pythonhosted.org/packages/95/a4/f796e94b43a66704b6ae17c747c7b97fd4b79348f1cfa9bef7b008aaa718/pyobjc_framework_webkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:00ffb254f97e9ffdd0a82c1faa61a07f6072ba900fa8aba70c83c21198b52e4e", size = 50853, upload-time = "2026-06-19T16:19:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/d24716fef19ccc3d880e99029458803f0174c05df310d991eb97ea3a0799/pyobjc_framework_webkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:67030258c3cd66e8495ccfccef3d2d58010ff0209284c5115e5afdb0e9fd6de1", size = 50499, upload-time = "2026-06-19T16:19:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6c/817119a52efcc229a30ceff56a0641005a431806a1f555e0571626ba313a/pyobjc_framework_webkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5d91527c9950c79269dd0d70f2bb8668c298dd06930637c1c063ce5f274a87e5", size = 50967, upload-time = "2026-06-19T16:19:35.474Z" }, + { url = "https://files.pythonhosted.org/packages/2d/59/5fac0754d53b2a72aed6f424dfc72e5fa245f83cb57c2e00d02e45390fca/pyobjc_framework_webkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:657825081484c9920c50b76b469b9583f116225b0449c9d95c46cbc8c640adc8", size = 50498, upload-time = "2026-06-19T16:19:36.397Z" }, + { url = "https://files.pythonhosted.org/packages/da/0c/e997e33d99d4ad91da2cf70f0e51ac39b03c58ba210548e9e944bbb421be/pyobjc_framework_webkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:f46adcc6227873f2b14d74b2e789c937f227722274ab59b9fa3c04c6ecb46dd5", size = 50958, upload-time = "2026-06-19T16:19:37.424Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytensor" +version = "2.31.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "cons", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "etuples", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "logical-unification", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "minikanren", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/1b/77783110a06ce0afacab063c64f644ea0a450daffa76dcf1bbb09ae2d819/pytensor-2.31.7.tar.gz", hash = "sha256:0af99e240c95bc0223886eefb4343b0e9dc6fba349b70b107b3a6fbb9cb66409", size = 4431862, upload-time = "2025-07-09T00:34:30.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/75/ea4fb7bc3252f313c7a4d0d124aa9c8f020cb668300f3c4d742807383772/pytensor-2.31.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:142814427e0e82d8f3db83529faa6073d33ce9e571cb22096547af19016ad025", size = 1438513, upload-time = "2025-07-09T00:33:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/ca/48/2de01b59238517308348ba97624e85781e6061a622bef71616df54e52256/pytensor-2.31.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70e262a5c4be0055f25dbbb8512c1eb2899cb80e6a630b95ea387356a825f823", size = 1892231, upload-time = "2025-07-09T00:33:52.094Z" }, + { url = "https://files.pythonhosted.org/packages/c2/68/8123d9794351e1b22f8d1ff8e4dce770e722b22d5bd58c9b0bd397540383/pytensor-2.31.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2343df3dc9eb46903a4c68591dcc36a05b2c9309a0748a47e3933a3864765d4d", size = 1908596, upload-time = "2025-07-09T00:33:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2a/33b5eb1c537f79e13a2f3aaf429bc6abed86148208181c76af64ddf4c396/pytensor-2.31.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58927b06df3168e9df005dadc2869613f4fba12ed6b49096e8a3fc29583aaa9d", size = 1918935, upload-time = "2025-07-09T00:33:56.32Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a2/3937ad756662120492ea926e22ba62c7c0606694139479e458d806ed9125/pytensor-2.31.7-cp310-cp310-win_amd64.whl", hash = "sha256:2ae4fca59c4858353b4025b48a09b07d80bced723369355b2284e357e5f15299", size = 1437758, upload-time = "2025-07-09T00:33:59.303Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e8/bcc2b73e4e5d46663a10295358397c11dfb251ccc108e2129173532b80ce/pytensor-2.31.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5983ced37b9c91fe7fb6c8cc44b432e68bfb266ffc0cab145406fba578039acb", size = 1624436, upload-time = "2025-07-09T00:34:01.449Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3d/b2e1e1e5d99d5273fa317cbbd27f3d70d688c287e4e2ccd3d81a93040aaa/pytensor-2.31.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f37c7b0ec42e2f5a0509b1753a0becac029dd902dad59360e24d5270d30d9c", size = 2109404, upload-time = "2025-07-09T00:34:03.182Z" }, + { url = "https://files.pythonhosted.org/packages/7b/00/76c159227f82d7ad62b9d4e5a13c55404d615eb969c5e4388c40b72acdef/pytensor-2.31.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:44bf1e2ee2e49cfbc95cc9fbeac4534dc18fc325735ad13ae8ef585cce7b7bf8", size = 2123068, upload-time = "2025-07-09T00:34:05.37Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ed/acf9bbb19f57cf8308700fdb41990163196ce10bd2dec15cbdc429609933/pytensor-2.31.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ff160177c7209f5803715e297d9052eff968f4ae50e3ad787ee496bf0fd53af", size = 2138152, upload-time = "2025-07-09T00:34:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/6d/03/8eaf7204149b92cf3987c1ec1d9f976fef9ccf0a732e51eee9844aee1476/pytensor-2.31.7-cp311-cp311-win_amd64.whl", hash = "sha256:38acc41d2724fad9f87795106754b5e79f9374ec35450a0b3382c5fbe01bc850", size = 1622332, upload-time = "2025-07-09T00:34:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/92e2bd5b6752780703685a818e2d4d96360a3906bf58985a383cf84bd03d/pytensor-2.31.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c863122dc61638aa87c7277da984fe266eba6cf53e04a0cf5acbeac1fbe4e325", size = 1624386, upload-time = "2025-07-09T00:34:10.678Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/9205f8420f59466a36b18ca41f09767ff0aed48ddb2ecb2c6d170bd201e8/pytensor-2.31.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1489a55acb192528974f343f8cc67fc53cac0fbeb0bc02d4281f0a207fa506b5", size = 2100754, upload-time = "2025-07-09T00:34:12.314Z" }, + { url = "https://files.pythonhosted.org/packages/9b/51/676da3fcf776fd50af0b7e9d5e662c584a23456afeb8598fa87135b61af9/pytensor-2.31.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e79aaea8e1a94d115bcd895c82f8a6bf0dd651518a980108ca718ff182afb24b", size = 2115814, upload-time = "2025-07-09T00:34:14.325Z" }, + { url = "https://files.pythonhosted.org/packages/52/62/7f2ff99f244afbe318f45cb69c6e548e7135caf4a5b89b5b32a6646af59b/pytensor-2.31.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5aab6ff129ed85910b42965eef4a58b53d072c123200763677b6787a165d1aab", size = 2139405, upload-time = "2025-07-09T00:34:15.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/bebc6fadc66ef513c7b87a5d1de46e522ff4d57bd1acfb39f5f400b2b06e/pytensor-2.31.7-cp312-cp312-win_amd64.whl", hash = "sha256:ab478a722dedc89e31634f189343a416293e27555be6ea6f4620598f05ecd583", size = 1623709, upload-time = "2025-07-09T00:34:17.947Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/9c5dedd5c2616fabf1f94b40adf0defde1f4c7dc8afc9319bf60da5611af/pytensor-2.31.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2c3b463f392bb46d99ce9f060faaae13420277f446de5f4d6c18ac095bf83439", size = 1623518, upload-time = "2025-07-09T00:34:19.584Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e4/47e7e2818c8b1acbd64f9a0ab3de001e484fb3545c82b37c350d71eb9e7b/pytensor-2.31.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5abd20541c9d9a4340fde4da63cab5a6b4ec3228f0070b539bb6188f2255512", size = 2094291, upload-time = "2025-07-09T00:34:21.772Z" }, + { url = "https://files.pythonhosted.org/packages/a9/fd/6d75179820edb49ed42467e3f78ba1c9bf305f1bdc65524f97de4578114b/pytensor-2.31.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99468fb50b3765b43bb82a3f411dcd37b3f5ce922870d8374ddf9cbc01033d68", size = 2116022, upload-time = "2025-07-09T00:34:24.007Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1e/6ff8891aaee7295b8c5d62149832cf38e8af141e703f69263bdb47564e6a/pytensor-2.31.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e7df2d29568602544eb1733d09075a6f229470fc6b96fc03ba8036997eef58a7", size = 2134927, upload-time = "2025-07-09T00:34:25.865Z" }, + { url = "https://files.pythonhosted.org/packages/61/f3/e341e9d5d20f31a15beaa61dd16b3254db93542830f3814acf5e895b27c0/pytensor-2.31.7-cp313-cp313-win_amd64.whl", hash = "sha256:f1b533058ad2503aa40df7db3aec99cfb1eff5c158ec7cb58892db940ff585ac", size = 1623504, upload-time = "2025-07-09T00:34:27.511Z" }, + { url = "https://files.pythonhosted.org/packages/0a/3d/33859b753186d3bcae89fd2ddfd5b180569030db3ace02a26c0d3b56c449/pytensor-2.31.7-py2.py3-none-any.whl", hash = "sha256:d7f89c7eaedd8ce4323289602b41be28e8aaee14c73a52c701079f25c4247aa8", size = 1338497, upload-time = "2025-07-09T00:34:28.942Z" }, +] + +[[package]] +name = "pytensor" +version = "2.38.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "cons", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "etuples", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "logical-unification", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "minikanren", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/89/e7b91f7366e36dbf937d4e53fa949b7f93627812e833526e0e426becbacb/pytensor-2.38.2.tar.gz", hash = "sha256:c804e665e69e830e31344133fea5793d2fd75c662ee1359d01589875c0cce105", size = 4862054, upload-time = "2026-03-06T13:37:01.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4a/c148b5d7df8bc3c3b4cb521ae955a2f312b3f75a931f25c52b34b8f32728/pytensor-2.38.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea3f63c9f5eeecbe44be0305fa031b4c7352f0278dade695f22fa1c3703fcebf", size = 1506543, upload-time = "2026-03-06T13:36:25.322Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c5/be1088c0cfa06e972be51d83e8058ef87a98cbc87faf86502e395df0854e/pytensor-2.38.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34eb065cf0ff2ed5845adedf9fe64ea67500dfb09aac211ec0ae0a6680651840", size = 2005244, upload-time = "2026-03-06T13:36:27.598Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ed/1ac83d3c4993414859929a24cfbb4ebf00e5c61fc10fbc4ac1d6262092e0/pytensor-2.38.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a7d2251ba1bd22649092af1d1661c339b3d2fcb938be663b3b60f2435a8addd9", size = 2005883, upload-time = "2026-03-06T13:36:30.069Z" }, + { url = "https://files.pythonhosted.org/packages/11/17/2c00b0cfcfa066f70b9d905a88a8de24f409310f51d205c3bb4163316b64/pytensor-2.38.2-cp311-cp311-win_amd64.whl", hash = "sha256:c06f596acd7659471def911ee6a1e35013357e18c276777b1eb98bff316f310c", size = 1497308, upload-time = "2026-03-06T13:36:31.864Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c0/caaecaddedb0919cdde1d943da877378d8e7ca6efe4d1ba721e6ba9b4901/pytensor-2.38.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78227922e8e282b4af8cc98d496fbca2aead90da80221ed937f809e3e22a7d8e", size = 1698818, upload-time = "2026-03-06T13:36:33.968Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/2269f04fc08579fc3133507ddc1a85c6121bb031b592085f85767eeb0c24/pytensor-2.38.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b95d3f31c90f5269636fea0539b406c68e9d7af8d2cca25c973d13d7628c7a7a", size = 2198280, upload-time = "2026-03-06T13:36:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cf/f4194459d4dd8952dcc01352a63b21b277b94200e1d12ac026e078598cf3/pytensor-2.38.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e84086b77e0ed2577b8aa95ed2e398d25f2773cace9b30185b8ecac26d135529", size = 2196311, upload-time = "2026-03-06T13:36:38.644Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1e/04ead3ab7ad4fdfcc48bafb38e4f0e4b175516c8b5c82526f536f79cc6d8/pytensor-2.38.2-cp312-cp312-win_amd64.whl", hash = "sha256:2393c7a0a8782fd447415971303111f485400390d15b356a62332efe36f9ba3a", size = 1691887, upload-time = "2026-03-06T13:36:40.594Z" }, + { url = "https://files.pythonhosted.org/packages/07/8a/9d1a8e4c70b48ea07bc0f66004afc406612748e98c036b6c428814277baa/pytensor-2.38.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6857cc173df3b9362e8730db6e0ff4552feff9c86f86be98a92794a50305fda0", size = 1698193, upload-time = "2026-03-06T13:36:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/92/e0/03a949eebcac4ff320263514ae65f939ed10f98c5523a878223f6cb3b18b/pytensor-2.38.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03cb6307d29ce4a1755d82b6ccaa6739a070bd2a29223a12cbf4906b7ceefb94", size = 2197111, upload-time = "2026-03-06T13:36:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/4f/52/e4a39c0377c435d4532bce95cb2ba8e161ee383b51e425bd08cadc780d79/pytensor-2.38.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bd0e71d7e543df920a297601a1dc01441711b311d9e6cfb3c5cd1a5a10ab87a3", size = 2195311, upload-time = "2026-03-06T13:36:46.213Z" }, + { url = "https://files.pythonhosted.org/packages/97/4a/fb34aefd1a144708fe454004deab4393c45f94f5d2ba7bbe2610994d7699/pytensor-2.38.2-cp313-cp313-win_amd64.whl", hash = "sha256:3174e506c105a260c041c00e019e5ef2b2561a0fe1e51ddcefb6bc8f7baf7c17", size = 1691597, upload-time = "2026-03-06T13:36:47.719Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a7/eea67f7b112053755fbdc071e8aeb27184e1ac651d031ef9f75eabb43fe8/pytensor-2.38.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:071609e3a10b91d8aa11a4124f323836381fb8b92a4d424862b07f94a1d63b5c", size = 1699470, upload-time = "2026-03-06T13:36:49.433Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/9667de1da8f38ad8556a62847330b07e2f2a06664c35ef3ca9b60b55896c/pytensor-2.38.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:994b051ec298789a9133cad29aa36092e0c2a3a564f64a2f05211c54d759b619", size = 2189740, upload-time = "2026-03-06T13:36:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4a/8bf706ac30c026e01971e4cc6ef712677a635c33ce581514781658b1d975/pytensor-2.38.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed8898ecae457ad621bc6610f13de37f83565a61782aa6c4c5e289d9c0310ab3", size = 2190574, upload-time = "2026-03-06T13:36:53.731Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/215b8860b2409737cacca387644617dd0bb333a60d5bb77c3d7e0aa3e89c/pytensor-2.38.2-cp314-cp314-win_amd64.whl", hash = "sha256:9c4b4af108e9f9c4b664be0d10da0e5dfeb213bd2b83b94c6378d39a9f23915f", size = 1694447, upload-time = "2026-03-06T13:36:56.413Z" }, + { url = "https://files.pythonhosted.org/packages/4c/12/4b91d7ec085f35f6e0afbf57c9d4a13a45d48d9bef37ed6123d147255ecf/pytensor-2.38.2-py2.py3-none-any.whl", hash = "sha256:ae994b6e96d0d536e38c175a9b68df39fce4685a71dc8bb40acfbb779e755a34", size = 1399422, upload-time = "2026-03-06T13:36:58.363Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[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 = "pythonnet" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "clr-loader", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu') or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/57/da1992e44663b71365c6e842c8d7fa453d4ec45fb99a68cfee5b7e944d3c/pythonnet-3.1.0.tar.gz", hash = "sha256:7b34c382905d10a371509ffafd64cae0416305c28817738a9cd138336f4e9991", size = 250599, upload-time = "2026-05-23T20:30:21.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/4b/52414f442624d2589f5374a48c08d5ae94f24bea67fc13a20a752884e5b7/pythonnet-3.1.0-cp310.cp311.cp312.cp313.cp314-none-any.whl", hash = "sha256:698dd88edc198819ad63b624a6ebe76208c7b46e4fe13626f65e484f0358d6ba", size = 217578, upload-time = "2026-05-23T20:30:19.527Z" }, + { url = "https://files.pythonhosted.org/packages/db/67/031124fdcb937c266a3265118525bbf6dc13b8c79786d6a7290aecb6e7bb/pythonnet-3.1.0-cp310.cp311.cp312.cp313.cp314-none-win32.win_amd64.whl", hash = "sha256:7bdd4de03df3547a48122a3989265c8b31d5be0d19dadffa009eec7df8085e0b", size = 1644898, upload-time = "2026-05-23T20:30:16.213Z" }, +] + +[[package]] +name = "pytz" +version = "2026.3.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, +] + +[[package]] +name = "pywebview" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bottle" }, + { name = "proxy-tools" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pythonnet", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "qtpy", marker = "sys_platform == 'openbsd6' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/4a/05307135dafba67778669d194bd1a3822a7685ec9ee8a6d7e70856c1a551/pywebview-6.2.1.tar.gz", hash = "sha256:71b7136752e40824655304d938efb62014218d1a90bd8e87e1cbdb1ce9c466af", size = 513126, upload-time = "2026-04-15T09:02:16.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/25/9491695c22c4842c5b3903b4dc172e0eecf67a27c0af34a71512c9b76a0a/pywebview-6.2.1-py3-none-any.whl", hash = "sha256:9d07275f53894ab4d5e2e0e996227193e7187dec276d9b624dccbce029216b46", size = 525463, upload-time = "2026-04-15T09:02:10.186Z" }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/1b/9cfdeac80ee45bebbbcb31f1b7b99a0d81a1c72de48d837be984e0e88b1d/pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e", size = 6361387, upload-time = "2026-06-04T07:49:14.329Z" }, + { url = "https://files.pythonhosted.org/packages/33/b1/7afc96d041d982c27bc2df6f853d43f01fd273e3d39d04be3647ddeb533d/pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db", size = 6926780, upload-time = "2026-06-04T07:49:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/4140da9ad54108e517f4a16b2d83da3033e08662144623e1239587cb7db6/pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd", size = 4307203, upload-time = "2026-06-04T07:49:18.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" }, + { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "qtpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu') or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" }, +] + +[[package]] +name = "raiutils" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/4b/ec9518b3f59b38e14be6db4863bfe021e05fab8434bd883f416fbea93351/raiutils-0.4.2.tar.gz", hash = "sha256:d210a4d5a059e48388d341ee02cb87f3c92bbf1f0bcbcecf04fd93a599d2dca4", size = 19817, upload-time = "2024-04-15T21:13:58.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/81/dde454fb014545f8e3b35b49947e9093f255e95a6ebc2883c75e6d9f8598/raiutils-0.4.2-py3-none-any.whl", hash = "sha256:69b8966c1f5f9ba8e5c4b8ff802b3cd3a379f3a1234f9e412369315d87998192", size = 17554, upload-time = "2024-04-15T21:13:57.117Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +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 = "regex" +version = "2026.7.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" }, + { url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" }, + { url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" }, + { url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" }, + { url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" }, + { url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" }, + { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778, upload-time = "2026-07-19T00:17:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983, upload-time = "2026-07-19T00:17:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961, upload-time = "2026-07-19T00:17:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708, upload-time = "2026-07-19T00:17:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651, upload-time = "2026-07-19T00:17:13.312Z" }, + { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756, upload-time = "2026-07-19T00:17:15.015Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798, upload-time = "2026-07-19T00:17:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933, upload-time = "2026-07-19T00:17:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338, upload-time = "2026-07-19T00:17:21.057Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452, upload-time = "2026-07-19T00:17:23.119Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958, upload-time = "2026-07-19T00:17:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765, upload-time = "2026-07-19T00:17:26.993Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714, upload-time = "2026-07-19T00:17:28.699Z" }, + { url = "https://files.pythonhosted.org/packages/5d/47/e02db4015d424fc83c00ea0ac8c5e5ec14397943de9abf909d5ce3a25931/regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5", size = 267157, upload-time = "2026-07-19T00:17:31.051Z" }, + { url = "https://files.pythonhosted.org/packages/08/8e/c780c131f79b42ed22d1bd7da4096c2c35f813e835acd02ef0f018bd892c/regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312", size = 277777, upload-time = "2026-07-19T00:17:32.848Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4c/e4d7e086449bdf379d89774bf1f89dc4a41943f3c5a6125a03905b34b5fb/regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d", size = 277136, upload-time = "2026-07-19T00:17:34.803Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3d/84165e4299ff76f3a40fe1f2abf939e976f693383a08d2beea6af62bd2c1/regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40", size = 496552, upload-time = "2026-07-19T00:17:36.808Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/a65293e6e4cf28eb7ee1be5335a5386c40d6742e9f47fafc8fec785e16c7/regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38", size = 296983, upload-time = "2026-07-19T00:17:38.816Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/2d0564e93d87bc48618360ddca232a2ca612bbdf53ce8465d45ca5ce14ee/regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11", size = 291832, upload-time = "2026-07-19T00:17:40.726Z" }, + { url = "https://files.pythonhosted.org/packages/07/cd/42dfbabff3dfc9603c501c0e2e2c5adbb09d127b267bf5348de0af338c15/regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13", size = 796775, upload-time = "2026-07-19T00:17:42.382Z" }, + { url = "https://files.pythonhosted.org/packages/df/5d/f6a4839f2b934e3eed5973fd07f5929ee97d4c98939fb275ea23c274ee16/regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae", size = 865687, upload-time = "2026-07-19T00:17:44.185Z" }, + { url = "https://files.pythonhosted.org/packages/14/b0/b47d6c36049bc59806a50bd4c86ced70bbe058d787f80281b1d7a9b0e024/regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da", size = 911962, upload-time = "2026-07-19T00:17:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/ff61f28f9273658cfe23acbbac5217221f6519960ed401e61dfdab12bc35/regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15", size = 801817, upload-time = "2026-07-19T00:17:48.25Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bb/8b4f7f26b333f9f79e1b453613c39bb4776f51d38ae66dd0ba31d6b354ca/regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f", size = 776908, upload-time = "2026-07-19T00:17:50.183Z" }, + { url = "https://files.pythonhosted.org/packages/09/13/610110fc5921d380516d03c26b652555f08aa0d23ea78a771231873c3638/regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939", size = 784426, upload-time = "2026-07-19T00:17:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f5/1ef9e2a83a5947c57ebff0b377cb5727c3d5ec1992317a320d035cd0dbb6/regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96", size = 860600, upload-time = "2026-07-19T00:17:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/a0/02/073af33a3ec149241d11c80acea91e722aa0adbf05addd50f251c4fe89c3/regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220", size = 765950, upload-time = "2026-07-19T00:17:56.041Z" }, + { url = "https://files.pythonhosted.org/packages/81/a9/d1e9f819dc394a568ef370cd56cf25394e957a2235f8370f23b576e5a475/regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc", size = 851794, upload-time = "2026-07-19T00:17:57.897Z" }, + { url = "https://files.pythonhosted.org/packages/03/3a/8ae83eda7579feacdf984e71fb9e70635fb6f832eeddca58427ec4fca926/regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2", size = 789845, upload-time = "2026-07-19T00:17:59.97Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/c195cbfe5a75fdec64d8f6554fd15237b837919d2c61bdc141d7c807b08b/regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404", size = 267135, upload-time = "2026-07-19T00:18:01.958Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/a11de8404b7272b70acb45c1c05987cce60b45d5693da2e176f0e390d564/regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e", size = 277747, upload-time = "2026-07-19T00:18:04.121Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/0f5c8eff1b4f1f3d83276d365fccecf666afcc7d947420943bf394d07adb/regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8", size = 277129, upload-time = "2026-07-19T00:18:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4c/44b74742052cedda40f9ae469532a037112f7311a36669a891fba8984bb0/regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2", size = 501134, upload-time = "2026-07-19T00:18:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/f0/45/bbd038b5e39ee5613a5a689290145b40058cc152c41de9cc23639d2b9734/regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda", size = 299418, upload-time = "2026-07-19T00:18:09.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/38/c5bde94b4cedfd5850d64c3f08222d8e1600e84f6ee71d9b44b4b8163f74/regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff", size = 294486, upload-time = "2026-07-19T00:18:11.188Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6a/2f5e107cb26c960b781967178899daf2787a7ab151844ed3c01d6fc95474/regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1", size = 811643, upload-time = "2026-07-19T00:18:12.975Z" }, + { url = "https://files.pythonhosted.org/packages/37/d4/a2f963406d7d73a62eed84ba05a258afb6cad1b21aa4517443ce40506b78/regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf", size = 871081, upload-time = "2026-07-19T00:18:14.733Z" }, + { url = "https://files.pythonhosted.org/packages/45/a3/44be546340bedb15f13063f5e7fe16793ea4d9ea2e805d09bd174ac27724/regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732", size = 917372, upload-time = "2026-07-19T00:18:16.724Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f6/e0870b0fd2a40dba0074e4b76e514b21313d37946c9248453e34ec43923e/regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a", size = 816089, upload-time = "2026-07-19T00:18:18.617Z" }, + { url = "https://files.pythonhosted.org/packages/ae/27/957e8e22690ad6634572b39b71f130a6105f4d0718bb16849eac00fff147/regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba", size = 785206, upload-time = "2026-07-19T00:18:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/a4/186e410941e731037c01166069ab86da9f65e8f8110c18009ccf4bd623ee/regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc", size = 800431, upload-time = "2026-07-19T00:18:22.716Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/e4e10e023d291d64a33e246610b724493bf1ce98e0e59c9b7c837e5acfb7/regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62", size = 864906, upload-time = "2026-07-19T00:18:24.772Z" }, + { url = "https://files.pythonhosted.org/packages/24/57/ccb20b6be5f1f52a053d1ba2a8f7a077edb9d918248b8490d7506c6832b3/regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1", size = 773559, upload-time = "2026-07-19T00:18:27.008Z" }, + { url = "https://files.pythonhosted.org/packages/a3/82/f3b263cf8fad927dc102891da8502e718b7ff9d19af7a2a07c03865d7188/regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e", size = 857739, upload-time = "2026-07-19T00:18:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/1687bd1b6c2aed5e672ccf845fc11557821fe7366d921b50889ea5ce57bf/regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0", size = 804522, upload-time = "2026-07-19T00:18:31.362Z" }, + { url = "https://files.pythonhosted.org/packages/76/7c/cc4e7655181b2d9235b704f2c5e19d8eff002bbc437bae59baee0e381aca/regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4", size = 269141, upload-time = "2026-07-19T00:18:33.479Z" }, + { url = "https://files.pythonhosted.org/packages/bb/14/961b4c7b05a2391c32dbc85e27773076671ef8f97f36cec70fe414734c02/regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974", size = 280036, upload-time = "2026-07-19T00:18:35.419Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/795644550d788ddbb6dc458c95895f8009978ea6d6ea76b005eb3f45e8c9/regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d", size = 279394, upload-time = "2026-07-19T00:18:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/d2/25/0c4c452f8ef3efe456745b2f33195f5904b573fb4c2ff3f0cb9ec188461e/regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd", size = 496750, upload-time = "2026-07-19T00:18:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/24/9e/b70ca6c1704f6c7cd32a9e143c86cc5968d10981eca284bad670c245ea7d/regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac", size = 297093, upload-time = "2026-07-19T00:18:41.583Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/0b692da2520d51fbff19c88b83d97e4c702909dd02386c585998b7e2dbed/regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5", size = 292043, upload-time = "2026-07-19T00:18:43.347Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a7/1d478e614016045a33feae57446215f9fd65b665a5ceb2f891fb3183bc52/regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3", size = 797214, upload-time = "2026-07-19T00:18:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ae/11b9c9411d92c30e3d2db32df5a31133e4a99a8fc397a604fd08f6c4bffb/regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053", size = 866433, upload-time = "2026-07-19T00:18:47.315Z" }, + { url = "https://files.pythonhosted.org/packages/b1/62/2b2efc4992f91d6d204b24c647c9f9412e85379d92b7c0ab9fdae622327e/regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b", size = 911360, upload-time = "2026-07-19T00:18:49.588Z" }, + { url = "https://files.pythonhosted.org/packages/14/71/986ceea9aa3da548bf1357cad89b63915ec6d21ec957c8113b29ece567df/regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a", size = 801275, upload-time = "2026-07-19T00:18:51.767Z" }, + { url = "https://files.pythonhosted.org/packages/15/be/ce9d9534b2cda96eab32c548261224b9b4e220a4126f098f60f42ae7b4cd/regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1", size = 777131, upload-time = "2026-07-19T00:18:54.053Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/58b5c710f2c3929515a25f3a1ca0dad0dcd4518d4fff3cf23bc7adb8dcd2/regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e", size = 785020, upload-time = "2026-07-19T00:18:56.579Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/5fe091935b74f15fe0f97998c215cae418d1c0413f6258c7d4d2e83aa37f/regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12", size = 861263, upload-time = "2026-07-19T00:18:58.64Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/d60bf82e10841eef62a9e32aac401468f05fddfbcb2942e342b1ba3d2433/regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2", size = 766199, upload-time = "2026-07-19T00:19:00.705Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5d/11e64d151b0662b81d6bf644c74dc118d461df85bdf2577fadbbf751788a/regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97", size = 851317, upload-time = "2026-07-19T00:19:03.015Z" }, + { url = "https://files.pythonhosted.org/packages/7c/34/532efb87488d90807bae6a443d357ee5e2728a478c597619c8aaa17cc0bd/regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4", size = 789557, upload-time = "2026-07-19T00:19:05.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/3a8d5ca977171ec3ae21a71207d2228b2663bde14d7f7ef0e6363ecf9290/regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa", size = 272531, upload-time = "2026-07-19T00:19:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/96/e1/8862885e70409de70e8c005f57fb2e7be8d9ef0317250d60f4c9660a300d/regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac", size = 280831, upload-time = "2026-07-19T00:19:09.46Z" }, + { url = "https://files.pythonhosted.org/packages/08/82/2693e53e29f9104d9de95d37ce4dd826bd32d5f9c0085d3aa6ac042675c4/regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459", size = 281099, upload-time = "2026-07-19T00:19:11.398Z" }, + { url = "https://files.pythonhosted.org/packages/92/b7/9a01aa16461a18cde9d7b9c3ab21e501db2ce33725f53014342b91df2b0a/regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3", size = 501121, upload-time = "2026-07-19T00:19:13.425Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/bbaeca815dc9191c424c94a4fdc5c87c75748a64a6271821212ebdd4e1a3/regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518", size = 299415, upload-time = "2026-07-19T00:19:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d6/0dd1a321afaab95eb7ff44aa0f637301786f1dc71c6b797b9ed236ed8890/regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9", size = 294483, upload-time = "2026-07-19T00:19:17.879Z" }, + { url = "https://files.pythonhosted.org/packages/92/5f/40bacf91d0904f812e13bbbab3864604c463eced8afdc54aeaa50492ea95/regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435", size = 811833, upload-time = "2026-07-19T00:19:20.102Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/4902744261f775aeede8b5627314b38482da29cf49a57b66a6fb753246c5/regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0", size = 871270, upload-time = "2026-07-19T00:19:22.365Z" }, + { url = "https://files.pythonhosted.org/packages/16/70/6980c9be6bf21c0a60ed3e0aea39cf419ecf3b08d1d9947bc56e196ef186/regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a", size = 917534, upload-time = "2026-07-19T00:19:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/8b2bd872782ce8c42691e39acb38eb8efe014e5ddb78ad7d943d6f197ce9/regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276", size = 816135, upload-time = "2026-07-19T00:19:26.919Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/33a602f657bdc4041f17d79f92ab18261d255d91a06117a6e29df023e5e2/regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c", size = 785492, upload-time = "2026-07-19T00:19:29.192Z" }, + { url = "https://files.pythonhosted.org/packages/9e/36/0987cf4cb271680064a70d24a475873775a151d0b7058698a006cb0cae4a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a", size = 800658, upload-time = "2026-07-19T00:19:31.392Z" }, + { url = "https://files.pythonhosted.org/packages/a8/24/c14f31c135e1ba55fa4f9a58ca98d0842512bf6188230763c31c8f449e3b/regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009", size = 865073, upload-time = "2026-07-19T00:19:33.485Z" }, + { url = "https://files.pythonhosted.org/packages/14/85/181a12211f22469f24d2de1ebddfe397d2396e2c29013b9a58134a91069a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218", size = 773684, upload-time = "2026-07-19T00:19:35.599Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/bd1a0c1a62251366f8d21f41b1ea3c76994962071b8b6ea42f72d505c0f0/regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966", size = 857769, upload-time = "2026-07-19T00:19:37.738Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4f/f7e2dad6756b2fe1fe75dd90a628c3b45f249d39f948dd90cd2476325417/regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44", size = 804546, upload-time = "2026-07-19T00:19:40.229Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d7/01d31d5bdb09bc026fab77f59a371fdf8f9b292e4810546c56182ca70498/regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78", size = 274526, upload-time = "2026-07-19T00:19:42.398Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/cea4ce73bc0a8247a0748228ae6669984c7e1f8134b6fa66e59c0572e0ea/regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2", size = 283763, upload-time = "2026-07-19T00:19:44.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b6/26e41975febae63b7a6e3e02f32cff6cff2e4f10d19c929082f56aebf7c6/regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547", size = 283451, upload-time = "2026-07-19T00:19:46.639Z" }, +] + +[[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 = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rich-toolkit" +version = "0.20.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/3a/a258c2fbc6c6bdf428611388f5698ba5d57ffdf0755e1cab474d9cc47813/rich_toolkit-0.20.3.tar.gz", hash = "sha256:223dd2cfba325ed55e94933b9e53f3aca13e9fdf76622bd564c18109a2273c1b", size = 205355, upload-time = "2026-07-13T14:38:06.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/ce/639d0d0ce3d25c5edbd1afecd308bb35dc04883a45ac9f0855c8aee4e919/rich_toolkit-0.20.3-py3-none-any.whl", hash = "sha256:419aa87516d5f3849cca553c6dcf707c02a36d508fcf996946606725d34a3002", size = 36195, upload-time = "2026-07-13T14:38:05.687Z" }, +] + +[[package]] +name = "rignore" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/77/6ba90ab4a538d3ec244329c57c4d26a78c8313ea6fa72c8768d46f11c1c9/rignore-0.8.0.tar.gz", hash = "sha256:2e5ad6b19834f04a877d26fe863fd77ed851ed4019fdca097fb1b744311e3562", size = 55358, upload-time = "2026-07-17T19:01:21.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/10/0c27cdfa7ef3b68e3be77d4a1d711f87dcfdbe81e1e7fb83d591140e3437/rignore-0.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:74dcb42fbb3d1649d1738e2bddd97811e32adff56593dff40894288a5baf0019", size = 847554, upload-time = "2026-07-17T18:57:47.512Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/b96262e4d5149ba6c7f83c3fa82e2fcde21a8884369e2478c379b2721b4e/rignore-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e294b0d65a5371afa99188d8ccac8fdd231d47d445344e7bdf972cbfdc8876c5", size = 824518, upload-time = "2026-07-17T18:57:48.822Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4a/975513ea88875e1c3c2b3a1ba6c6237d41b94f2c09c344dae2e1eedb2fe6/rignore-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15296b259032f2923f3b63c0882d2e91ca2ffe58e539d216424eb0ad6669bae", size = 888154, upload-time = "2026-07-17T18:57:50.079Z" }, + { url = "https://files.pythonhosted.org/packages/39/82/3de813bdc06feb5eeccecfe6ab1ca42c5245f72eae50a9f4b0707c092615/rignore-0.8.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c8034f3da92632e4651c7f4b7674def0c394039209efccdb5fa0de7e11a637", size = 860698, upload-time = "2026-07-17T18:57:51.29Z" }, + { url = "https://files.pythonhosted.org/packages/17/9c/9644136cdd3f3984a73441620f236cfba1edb8e5442f23d7be1ca7d32049/rignore-0.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c441bfef6fedbebb80f4f8bf02086b275daefe0d4ec41025ee83b476413a2f8c", size = 1137843, upload-time = "2026-07-17T18:57:52.681Z" }, + { url = "https://files.pythonhosted.org/packages/4c/03/586e33e2ade54d211d8ef09e1f48522415e4d365fe1e339b4badb20eaa80/rignore-0.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c6d688562e36b80dfb2d5601da2c0873f0c9c46d47d57e9d01787d3f5ef266a", size = 917208, upload-time = "2026-07-17T18:57:54.298Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c9/a10ec7c0f27e425299640df655d7e9055021f9afe9d182c8e11c62ba3e6d/rignore-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11aca09b6aac3f4b669350434dd728244f83ddeaaecf9ce7e85f164976ece0fc", size = 932205, upload-time = "2026-07-17T18:57:55.948Z" }, + { url = "https://files.pythonhosted.org/packages/6f/31/c64c6659089bc23bb76ce88aa9bea5160f63d43564d8386eb16fe01eda68/rignore-0.8.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a847d46ceb3e97be98a00c6858bd7e5d9f0e712f2bb68a977cee76d85eb89934", size = 898483, upload-time = "2026-07-17T18:57:57.402Z" }, + { url = "https://files.pythonhosted.org/packages/3e/82/495346eb7353b061c2b9aac36d69c87291f3a6bfcfe2459dfb31aaef7c83/rignore-0.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac08f4333d7296b4b45a820ce056808891779117bebeb21cfd8dd46ab102ccd", size = 970643, upload-time = "2026-07-17T18:57:58.737Z" }, + { url = "https://files.pythonhosted.org/packages/19/15/e6e25bc9766337bb5fb662a7529249b540406ba37dd60f21fd805b175f60/rignore-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ba573086c3331d86ad81b27c4f2e4581ee2ca225dd621056e56e4ead6546b442", size = 1065243, upload-time = "2026-07-17T18:58:00.453Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1f/b4d07a45d01a1b150b2ab57300a1bac259046726c84e1dea231ca49a2155/rignore-0.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c3da9450e3f173fde3f8bc4cf8b2d4ed91fcd77f5d20135acc7773ee4ea8d039", size = 1135825, upload-time = "2026-07-17T18:58:01.76Z" }, + { url = "https://files.pythonhosted.org/packages/44/8d/09a23f9bd690e3e2ceddb70a82cbd361278038327a071426efaa5c3ffb86/rignore-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1ba8baf2fa91b673dc7cfb92b7c24f3f01f640584c1a125fdfe7a730e08a084d", size = 1147096, upload-time = "2026-07-17T18:58:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7c/d67d073f535e5596b6358d26357ecb3024be7cac85557febabc68c365ec5/rignore-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d5827d311722291be71d15c4d1a3ab0b0b9db86c1e0ca9121a65a67bc2732", size = 1142443, upload-time = "2026-07-17T18:58:04.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/f3/af265e5b0c94c76cd4b7fa0dcbedf38045856cacd3fa0e8b30910c5c0472/rignore-0.8.0-cp310-cp310-win32.whl", hash = "sha256:292621c65d7eca40f2bca87625eb59d5a11abc3d7e27cfbd15bbe3fc055e8de5", size = 642572, upload-time = "2026-07-17T18:58:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/a597d155cc86c2bfff20473c625568b925dfa09b30c79971a9d931edccd9/rignore-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:086118231d7c2f81529d56ee7d6f7b8f1d2544f2bd1a6e09224a6721cea8c270", size = 730890, upload-time = "2026-07-17T18:58:07.33Z" }, + { url = "https://files.pythonhosted.org/packages/85/56/a6b59a7236b5f9c48b190195f37c540b3e7bb733f3d4d955e47cee4c8f04/rignore-0.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e70cb5d20abb9ac80393e44073da44f014b5a3e8cdba94bca008fe966c48dc31", size = 847756, upload-time = "2026-07-17T18:58:08.694Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9d/2b4d611b9bec9a423eeb411fe790e33d91dbc698adddd8eaee910c761467/rignore-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae0d2990d492943ffccee9cd0ea9aa7d621b3ad20a7af34589393a9986a19d31", size = 824492, upload-time = "2026-07-17T18:58:09.869Z" }, + { url = "https://files.pythonhosted.org/packages/c7/be/5a98e66de445c2e6fc38263dba75ff083d62b4a0a6be8a097a9a0de23026/rignore-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2946af016739bcde8540206e88cf41ebbc571e29e03ac7181d745e58f121479f", size = 887313, upload-time = "2026-07-17T18:58:11.194Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ff/c53410f044b0f3d9f5a128f25de82a114bf446fefdf7cfcf0110db24b202/rignore-0.8.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d5c72b4bab8b2c5124b8ab72e364e2520103aa51b61ab363a8101a8306321342", size = 860851, upload-time = "2026-07-17T18:58:12.393Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/1eef3325df4914f903e646514e4f8d52cd48510b78ed09f43f2cf055cbd9/rignore-0.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e07132ec8778ccbd3617b5d2b6fed5eb890a6653cedf228e096821ac9b566daf", size = 1138682, upload-time = "2026-07-17T18:58:14.002Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8d/84a25517b3d41a5837c76ff3f3c888e41a9473add0048b4ac16d80fb4b62/rignore-0.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01f0ba125477766c4e335f55258fbe3e113d8ee3b21fd9accf87951416d88a47", size = 916927, upload-time = "2026-07-17T18:58:15.192Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/ae7ddcbfab868071874ca4f0eda9c200056fbb3bdc7ef0e5fdadec394822/rignore-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c86151db677c6b9c7b12389db73780cce3bd7125d7fb7a9160f57c5cdd310ed", size = 932289, upload-time = "2026-07-17T18:58:16.933Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e0/30f5dd7405ab8c0e7d1b7f332749eba3618fd40712bec389f7dddc934778/rignore-0.8.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:779994f61053818fcff9fc055857cad73f332151288be06e9c978032373090e0", size = 898683, upload-time = "2026-07-17T18:58:18.217Z" }, + { url = "https://files.pythonhosted.org/packages/5c/11/36bdad6c855205ec21e5649114feda84e6cd08057ed12b3fc8613f630e76/rignore-0.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:93cc8871a2aa4ead31e5f6850a4c5f7ffe931ccbd45dc7b95759903e1f56da69", size = 969733, upload-time = "2026-07-17T18:58:19.513Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b4/5c0799233b240bcd8fe6ee8f8ed5d9c5afa7adc0405e6c72666e2b714b32/rignore-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e80a6a0b5f29282927df180d5d550bc589b940b367ed387b7ba8add78b720972", size = 1064381, upload-time = "2026-07-17T18:58:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/22/13/d6db08a302baf9d1a23053cc96ff2530026f63ae4e79359fc31e85d6752d/rignore-0.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d5de6676f240f7250df9c308790ea73cfbf7babdd24d493cb197a00b8b51a6ad", size = 1136433, upload-time = "2026-07-17T18:58:22.408Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dd1f7d5dee97fca63df4dc9e70f35d4dbd76a3f06f0d2145667407cab1c7/rignore-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:112f4ac33266c5dbac4e2121b92f27718f110ced335bc91c97839a3514a8e706", size = 1146007, upload-time = "2026-07-17T18:58:24.923Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/61c9ff8f1ac8209bfd5de2706a4e8bfd27b06bc811d7286648fc07b0c944/rignore-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3b8996b2676870ac86ffada7858339e6a0765709225704310f08fa6ad2684672", size = 1142391, upload-time = "2026-07-17T18:58:26.211Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b1/85c03d8f99afab7275e3818a519e18ce5d316075dfee485648230a104844/rignore-0.8.0-cp311-cp311-win32.whl", hash = "sha256:2546f00278496b397f65eff6b005a7601820ade5c257cf651aab3b727760ab12", size = 640642, upload-time = "2026-07-17T18:58:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/3e/dc/9606977f377be00e65d5f0f7c3d93872f7390f050562552e1017222e6e05/rignore-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2f9c881bf31df4b4bab4ec44c838f511e4631ef06445690e77e96e6bd9cc943", size = 730849, upload-time = "2026-07-17T18:58:28.806Z" }, + { url = "https://files.pythonhosted.org/packages/41/69/deaf7f8e0a61bf0f2078fc5de8508c7ddd32e1a894de7029bf3862ea973d/rignore-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:4d057d29bc9bdd84d2e6eb083848c0e468198a3b5e4e50a19c92410ed85a9537", size = 668611, upload-time = "2026-07-17T18:58:29.961Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/0d270c1ed723b82bea8ccd504185acb5d71830975260e8de02af7acff728/rignore-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a5e285ec58b3b66284a7f48805e4db0ea948370deb484a4935b147187ecf1e25", size = 847184, upload-time = "2026-07-17T18:58:31.348Z" }, + { url = "https://files.pythonhosted.org/packages/ff/dc/841941f8b0883a8038f9d540607238c456df20e98243a61142fd15699806/rignore-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:367d3cf401b477a5ba7eb05b5b94c491b0d704507d9eaf80378a8d843fe00674", size = 816590, upload-time = "2026-07-17T18:58:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/57/53/9e047a6cd95b553519703350f7a3530ddd31d309c9ade1ec913db5882f74/rignore-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49870c032abcc28db2210ad92b3bd5706ec792ab25c9295a6c536a4b92226ce", size = 884130, upload-time = "2026-07-17T18:58:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/07/5a/ae444f30dfa47716ccadf9ed9512736494c0bf5222f330845a637f1c569c/rignore-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d50632823c273ee19212fd939b04e9b55d5a6e0c9b998fadef6dc9a0c2f6aecc", size = 857230, upload-time = "2026-07-17T18:58:35.32Z" }, + { url = "https://files.pythonhosted.org/packages/ba/67/b2ddfbf5a42a8ea89e6a0330189f7f76f9113e0d8c6a4c68b68ff68f139c/rignore-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3337e69856ba18c9d079e0ce8b342dc256f48585ae3e1c7aac67682b672e83a", size = 1133331, upload-time = "2026-07-17T18:58:36.895Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/291be16f7260ac87dedab41394f7738834bb1cd05d6e1b878eea8d598520/rignore-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1264c48aaff09c6431b428144e9e9fbd0d7864673f7bd752b51d2409971275e", size = 912520, upload-time = "2026-07-17T18:58:38.178Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/cd0df2def95ecfa93c781431ce7c58ea52f90742550ff327ed0f1f715f14/rignore-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8912e3cb4034b972ba75c9ecabb79bc41a1076cdf726e53019e45b71b82da56", size = 927109, upload-time = "2026-07-17T18:58:40Z" }, + { url = "https://files.pythonhosted.org/packages/e4/58/ce8af7214b903a2f86c3223dabd23e03a0c499df6559be5c9e2b15b4344c/rignore-0.8.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:43b20be0f1c8ae3f3a2575dbee4a38bd406608855f6e79cde8fda1c6bff9e203", size = 892314, upload-time = "2026-07-17T18:58:41.391Z" }, + { url = "https://files.pythonhosted.org/packages/47/3f/e8f4ec6cdf04d0cb9915590679771b55a6f579a7478704d1e6be45a2f792/rignore-0.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02cf38d96b6f54b69719b726877e308b34ac2c9c9f636dc8872ede2ff6825579", size = 962528, upload-time = "2026-07-17T18:58:42.736Z" }, + { url = "https://files.pythonhosted.org/packages/58/57/d10a43221644177f776d658ed9a03204e49d9bfd6805fbece841ea6720ac/rignore-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c820d3fa597ee419f3643f8c098a51249a868ca8515296a56a1505c01a8083d", size = 1060799, upload-time = "2026-07-17T18:58:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0e/d769dfa933861dd469c9158ffc9bc2cb3bc6d46f88000fb6f41cf2905a3a/rignore-0.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e3e863b2cb1db481384bd43a25730ef00f7eccdd49c82a0e1d481a6412dc2653", size = 1132462, upload-time = "2026-07-17T18:58:45.583Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3d/57fc6264ebf8d9b95850589899e6192d9f92b67ea48e56fc32969b75474a/rignore-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90cc363aedc7a93b4b15933f2104bfcae1c6c1635a9bc479665cae83044577d9", size = 1139679, upload-time = "2026-07-17T18:58:46.928Z" }, + { url = "https://files.pythonhosted.org/packages/fa/be/24b12a8464e19d348aeb388267f897ede0fae6618052c3148b747acae214/rignore-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:303a9fd02d3612d15e6dc3474ae7d3eae1d07b668b7b4943fd4df305be4d2f76", size = 1138125, upload-time = "2026-07-17T18:58:48.42Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8d/89f7cba3491164c04f8a64056ca973494536ffdf48bfba41565f54dfa122/rignore-0.8.0-cp312-cp312-win32.whl", hash = "sha256:e17a0914378fa15d1e29effce4b39fd2031f253e52d307ff4b71c443d1d5d30e", size = 637663, upload-time = "2026-07-17T18:58:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ff/823a5ead8bba0a2054cbf2dbef99989204557904ad8578f514bec1b9b4e7/rignore-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:d5c8f1dff84c4d4114f1553564b5d909a0c4c09dbe1b5916a0506d719f1fc3b5", size = 728265, upload-time = "2026-07-17T18:58:51.028Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ce/73c919505f9f270ee3e34205ff2dbe0b0d73e945f8c569922acff148bcf4/rignore-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:6cdea3f85de8286a38ae75a0f9092cd3afc4d33ce6ee2e3f6005f97f7da9d249", size = 665216, upload-time = "2026-07-17T18:58:52.308Z" }, + { url = "https://files.pythonhosted.org/packages/67/76/fc272f7a61bec353d321f04b6d4d6cf7cb1fe646e0e1bedd1abf187699bd/rignore-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:178d7a82b6f1dd0378efda2a69252f73240221ea727529ed67be788ad551cccf", size = 847049, upload-time = "2026-07-17T18:58:54.123Z" }, + { url = "https://files.pythonhosted.org/packages/a1/06/99ce87ef61c86670b3ffa1be6131bc73318a541c2d9d00edbfe00fe27b1f/rignore-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0cefccd353ab5f7436457c420a4058daba873552344eb731b124c6d25110fef9", size = 815664, upload-time = "2026-07-17T18:58:55.422Z" }, + { url = "https://files.pythonhosted.org/packages/1c/88/ecb7451631e493ce8d41d0cfa287a9aca891ca2b738ded4dd8be83cf1476/rignore-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd72c6eeec68a3354582e11168c702c55ff543af7a3e8b3f47bed63334d0a330", size = 884344, upload-time = "2026-07-17T18:58:56.82Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fe/24fe7e5f36c0ea0043017ee92ec6bbb980a6ca356e5fbe0ab3320a6c5482/rignore-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3e0f17f611ab32a505f32f3d6b6a08cc0e9ef4c596df07a8ebbc99225d07ca8b", size = 857024, upload-time = "2026-07-17T18:58:58.444Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e4/267814f4d4a96208f2d676d3e070b73d386a58dd45bad2bc2fd7d9babb27/rignore-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:828630c1710e3d21f79dd90b1939b1b8da4a49bdab857ce7fc11b58dd5ce509e", size = 1133041, upload-time = "2026-07-17T18:59:00.036Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b6/e6da8ed3ee26134bb1d08a5d181012c16a4bfeb4dfa5f461edf3d19dff95/rignore-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b074ea1fece686da847d2dc7a7f452af9f7a82e7bc9181f196104f1c0df4ebd", size = 912935, upload-time = "2026-07-17T18:59:01.333Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/4785a1673aa92b34bd2625b2c836debadfee4f6e394b273c86060f68b05b/rignore-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb7dfc95cae2deac47e5f75b6d8df041351664907e412ac39c3af44dae47ce0", size = 927344, upload-time = "2026-07-17T18:59:02.744Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8e/07d11d91c73f3723024ba5a1034818f6232f3c27c7bf194f2bc1eabb449c/rignore-0.8.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:80d3e468c2cd24be93eca4953ca7ea73dfaacf97ba674c54c61c8aad877d2bc3", size = 892736, upload-time = "2026-07-17T18:59:04.049Z" }, + { url = "https://files.pythonhosted.org/packages/5d/85/97f07d4c6737e6ae7dbef37b7eda5b7de1442167dc696483f7e002e2c8a9/rignore-0.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3ac6b6ce80d24015055d07b47a5157b8cb9b207bf9d4963f401b2c08e0263eb", size = 962660, upload-time = "2026-07-17T18:59:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6f/524ba5a14d2e297edc3ea5baece5eec38bdb8a40e717b86aecaa3b84aa48/rignore-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6200325937f8c6a24dab351e259fce6ea8fd744888118791074796f4041cb0a", size = 1060679, upload-time = "2026-07-17T18:59:06.867Z" }, + { url = "https://files.pythonhosted.org/packages/a6/84/699fdb9a0380ff1dc4910d7cb5d730dd823ba7b525d1c92cdb57023545cd/rignore-0.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:51b33030b83526e4284f2e115e04ff80f0ed8d992cd5bf389c275aaac534b829", size = 1132220, upload-time = "2026-07-17T18:59:08.211Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ac/c840f5992ece10f19f27607603b23864dd7a5fad6acb90eb43c52f02879e/rignore-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:48aa8acf2217d77cb4784fd5f9a95aaa73a85b9a337b7511679b1bc73f64b7d9", size = 1139449, upload-time = "2026-07-17T18:59:09.752Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1b/3f6f2a0a1b23e639f637394821c68bfb3a435349ed5ecd562af08c9a3e4f/rignore-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bc453de8490c76ab3905a18116d0f6555ee94540e1e69cca49e6675e9ca05888", size = 1138480, upload-time = "2026-07-17T18:59:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/98/b2/f5478b3850ad894f1480b36028b54868ba9c0069b7b13f4d78ff0a0391f7/rignore-0.8.0-cp313-cp313-win32.whl", hash = "sha256:0f03e964e7845583b1c344098d6e1b178e0e53ba7e496c8f148966b74dd2e5a9", size = 637473, upload-time = "2026-07-17T18:59:12.748Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/53ab47642c45222e4d47877d62e80ea79c0cf0b989a1b89f04244f14d6a4/rignore-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:71a00835b08987c86d6b7c14661a7a628c868bc649e2afbeb4962167561fbd11", size = 727646, upload-time = "2026-07-17T18:59:14.135Z" }, + { url = "https://files.pythonhosted.org/packages/0d/33/9793162cbe185c05869b41c029c341dca659008b1e55aefd0fd6f8cf67e3/rignore-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:28d20c22758b636936d8ae2a684b4ed2c64deea30410ca203c72cdfb4bbde0f1", size = 664864, upload-time = "2026-07-17T18:59:15.534Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ae/b7d365a5d103e527b3d23d11f19ab91ff5813a2509a236d8f56f608f03ff/rignore-0.8.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f7cd98185bb89d11676454df1617f00ca59473fe2c86471c7c7d8c97ed6359f6", size = 846928, upload-time = "2026-07-17T18:59:16.862Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4a/736d51756de8557be4ef2837d4c2eb6e3934887f1c04eb6ec1d0198b708d/rignore-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4101def5fdd1459ba107710382c18916c9981f961cad5196533ee4b06295f05b", size = 817297, upload-time = "2026-07-17T18:59:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/3799fa414d51779b4ffb71fb8fb861f886a592127cd28f88d81b1681dc4a/rignore-0.8.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a67b7f4485dbda470981c8ea7cdf270f2f9139169e86c8b57b9bb4649e79b7f6", size = 885129, upload-time = "2026-07-17T18:59:19.807Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3e/0d1771740b5ea32828e563fe8044e8ae6fbdf7edb70e69dc116b704a751e/rignore-0.8.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78d66ae2e95504743beccc7310aa0476dce27283e0341a22348f6528083dfbfe", size = 856976, upload-time = "2026-07-17T18:59:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/48/a1/4edeb2f03ef4cf74ca3518225415a4b036201cde43a6ca1a35a501481367/rignore-0.8.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa936504e875b5a3782d454a4121cb6912e4fb295bc0954a14e5a07b26aa27f8", size = 1136148, upload-time = "2026-07-17T18:59:22.565Z" }, + { url = "https://files.pythonhosted.org/packages/34/65/3f1cc51677e6225248ab1c374e4ed5db057c7d1bae538af3305ccb18ed54/rignore-0.8.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84a3dfd3edc7fe00dd368944f2305f13ddabfb08658e35f2f87ae51dc3e760cc", size = 915273, upload-time = "2026-07-17T18:59:24.079Z" }, + { url = "https://files.pythonhosted.org/packages/70/c3/90e3af9f983a7ca9e1b446edf7d9c07382232f74120a1699efea526776a2/rignore-0.8.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ae349a8fd54eb35cc804360b41ec71a55a574502054f574911204de193c067", size = 929366, upload-time = "2026-07-17T18:59:25.479Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c3/02e3aa7e4ccef1c5153fff3951efa5a84d4f5dbdc81f459e176083ca7443/rignore-0.8.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:19f9507bc3e037411f3a55355d954a8fc0a16f823dfb9566bd32f3aa0b934833", size = 892641, upload-time = "2026-07-17T18:59:27.083Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f2/1d7c1aaafded88178aa6ece604695ca886ce5df8d9f892beadd04c3b791c/rignore-0.8.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dedc11fe3ac6a4aa71f145b2c83ae8f4d07ba73dbb3fa56cd08aa3d770eec1e1", size = 962666, upload-time = "2026-07-17T18:59:29.17Z" }, + { url = "https://files.pythonhosted.org/packages/78/e4/0b9be868b4dae2d46d8aa5afdcce1a9884a7b60ece3117084674c5c4101d/rignore-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:52e31ba2c4ae5a5efa73ab11e11b11739ee4fa3fe8abcbdd7f040f888ab76765", size = 1061632, upload-time = "2026-07-17T18:59:30.542Z" }, + { url = "https://files.pythonhosted.org/packages/92/63/ea45b9bc66295c1d18ba98184c6242f57dd1619a4ac78d4a08c679b2b49f/rignore-0.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1cd08c9715bd216640cd95ffb766861f33ab012a52dc772cc53b4deb76440219", size = 1132097, upload-time = "2026-07-17T18:59:32.234Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1a/6f74fe3124f5901d01824ef172479bafc4a646137b1ba9e4d4c01cf99945/rignore-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad3ec7e37f7149b4eb9e1ab7a902fb505614ca14e12ba6f8e0308ef771e996b3", size = 1140242, upload-time = "2026-07-17T18:59:33.616Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/04951ceda27d83e473db84eadaccc1b308c8f1f0fb155ba0ce6b2ba61c3a/rignore-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:eb9ec9a4dd7085b0c0a4b6525e22c72444818c68a120f90f9ef20a5c993137b6", size = 1140353, upload-time = "2026-07-17T18:59:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/2c/56/3ad9ab039859cae2ab0ebf35c3eca679f1245c3568d5e8bc03ca74d6d3fb/rignore-0.8.0-cp314-cp314-win32.whl", hash = "sha256:3dc5c339a0ee2e351f0a4cec63f375739ba53897bacc76219678b0889650a506", size = 637993, upload-time = "2026-07-17T18:59:36.525Z" }, + { url = "https://files.pythonhosted.org/packages/f0/dc/866de21865bda01fea13638e706c1e5ebfa84e54619cff92cc2ea3b6b705/rignore-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:b11e160a6f6dac00270f732b65179c06414858f52ceef33e1d20348f2a7fd60b", size = 729317, upload-time = "2026-07-17T18:59:38.046Z" }, + { url = "https://files.pythonhosted.org/packages/af/03/7c4340f3675e0332030e2406ba4d5e2036db2158455eb4be1ee7a8754f90/rignore-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:a14d6b48a43cb802699ec91ff57211ce558c84ef64f8e40afe71370b37763804", size = 665377, upload-time = "2026-07-17T18:59:39.362Z" }, + { url = "https://files.pythonhosted.org/packages/a7/28/22d38ca061ce6e24f812ea2ab1992b300d3b2c66551ce065a960d9dfcabd/rignore-0.8.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e915da13ef7e1494c451cb363c24e9fba531cc7db93b4772d20e2f1902870d69", size = 845632, upload-time = "2026-07-17T18:59:40.704Z" }, + { url = "https://files.pythonhosted.org/packages/60/6e/aa828871acbd3e6dfe1448dfba5a13241684fd91e3d1d8823acafcf92ce5/rignore-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb93f83feaf10f80a03ef0ea7e7a794fffbb756a8932190860735d134de31115", size = 815701, upload-time = "2026-07-17T18:59:41.983Z" }, + { url = "https://files.pythonhosted.org/packages/9b/7c/7e9ef7ff0ffb0316fb8a3d0f38cc16f72d1596593c3ff2a75800f20e495a/rignore-0.8.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f8d4d436f067a5f724fc8ac8ba779a8c408806b7d32e126e9539b002a9e06c2", size = 884554, upload-time = "2026-07-17T18:59:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/6b/56/bfdb913c57ae26a6f0804dd7be26615a8cd7e39844cc0b6da3c50524515a/rignore-0.8.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a7ebedf2065bd1136754ae3dd5b5893da5ec98d23d683b942c810fe93677909d", size = 856054, upload-time = "2026-07-17T18:59:44.946Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cd/2312da57bdc365b7467d6857b0bcb74c08dfce59debec5dc8f243bbf0a99/rignore-0.8.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c92ca80e622102ec06d82606ed366c7c9e5e8d9d4f101ae3097a5e87e07f76e1", size = 1135507, upload-time = "2026-07-17T18:59:46.359Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/29cbde71521aa77540bd08523775219e48642876dfcfb9dd4f753933ede3/rignore-0.8.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c4eb2394fe45ef9abb64104e79c9b70a6a407708c965071072bcaac41735d11", size = 914693, upload-time = "2026-07-17T18:59:47.722Z" }, + { url = "https://files.pythonhosted.org/packages/5a/92/83182f17466ffb626060e5b5567cb163cfcef1f6264486b5270b55518472/rignore-0.8.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd84f82309b6737fcacbedfbc3611d58349a9e9c2a538a00262c2991478f9c", size = 927946, upload-time = "2026-07-17T18:59:49.369Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f0/f038a1b3d677716f63a39be9d56fd2d0d89c87eb5a3fe5a39c6e25799a85/rignore-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4d62068de382d18b2958fa2d3880baad97047c0cf18e8f46f5f49e3fd0d5d3a3", size = 891305, upload-time = "2026-07-17T18:59:50.992Z" }, + { url = "https://files.pythonhosted.org/packages/af/82/9e66abefcebfd5ea382d2c1f03b0e4900cea6a719f435ecc56339da03b05/rignore-0.8.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65bacba32c0c49f8c9499f919abc1c2e787dbd926cdbf69f49fdecb635e47673", size = 963896, upload-time = "2026-07-17T18:59:52.774Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f9/3a2697aba2e48e3e6875841c49222e7ba9a779f33f683ce9d7b16d2cd3c3/rignore-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1acd747ba926e8b35e3cda1dde17ea2b548fb58c6cf4b12cfc95559d064ae2c2", size = 1060829, upload-time = "2026-07-17T18:59:54.37Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9a/ba854d6937a0565ff669ce99aeef26a7250f12b6a3d92e2a6db12a9ebd8a/rignore-0.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2b25bbbd66c7dcd8198876a5dc3fb65efe075adce64cba62c120a5d6221ee7f", size = 1130894, upload-time = "2026-07-17T18:59:56.052Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/23824eca7320240b53ca1de83397a9fd92355f209b69902478a7d4f6dc1a/rignore-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a62125e3701f6731bf4fdbc32974783b5512937540b49559559d2ba0e086d36a", size = 1141322, upload-time = "2026-07-17T18:59:57.986Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5c/e06941a97cc49901ce93a9d045b5aef06a47288a5be283e092421d937754/rignore-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:69a07212f1b35a8f1cfce3df7f78a30ff8b81d2f4a223d40df5a78b770095fa7", size = 1138688, upload-time = "2026-07-17T18:59:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/ae/00a872a463b38c3d693acd096133fa693f68597879e943f78b98274ba2d4/rignore-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:f48bd7d720d96078411a7fd905d0e20fc41bb15e24cfa9d5914c6fa5b25c48f0", size = 638522, upload-time = "2026-07-17T19:00:01.154Z" }, + { url = "https://files.pythonhosted.org/packages/ff/22/fd0e37ba32fc70573501a0d0196faf3a91ebc066514d133399cd85d9cd7c/rignore-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:738613a5d9451df01810884059fae605047188e7cdcea79eb4b0f03d866348bd", size = 729624, upload-time = "2026-07-17T19:00:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e5/b99c0384bc72d6bc37db31158cab7a1ef068c8c3fc9080d4ca0e1c949308/rignore-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:caf1c51c60791cd9d6df46c2f82eed1634e1bf7859d116e36d56b9e69b1e9a71", size = 664583, upload-time = "2026-07-17T19:00:04.71Z" }, + { url = "https://files.pythonhosted.org/packages/ea/14/dbf3c030ed5452bd5fb176e44599b38bb94154c2d65d4548d6341b0ec845/rignore-0.8.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2b605cfd202a67857357e1cab7fdf3f235a185eb11c40a6d1a8dd246f59fa3af", size = 849768, upload-time = "2026-07-17T19:00:55.468Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cd/372861ee913c7dbf9f3bc748cfd544c961e26cde19a07985e7ade934c277/rignore-0.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8b88c75ddf2918b73f11df733696e49156716809509758efcfe0320d23534213", size = 825711, upload-time = "2026-07-17T19:00:57.108Z" }, + { url = "https://files.pythonhosted.org/packages/a5/47/2ea42b9bb25d3d3c757f70d996c75f4918718b71a3112bb0b3f731bcdf82/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16069c63e6fb791603b8d4c3afa9da838c4f024779b30b30b6fd66eb0ae5b938", size = 889267, upload-time = "2026-07-17T19:00:58.766Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/aed6e6505f16a0c8acdb01eafa021371750743a1ef54ed54027c422f55f6/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce8eaee6b8835e1946038885d740d54b6ebe4d29f4a7a85aedff7f19c86ca1d8", size = 862691, upload-time = "2026-07-17T19:01:00.717Z" }, + { url = "https://files.pythonhosted.org/packages/af/3a/0f16ee98483bef39a1dc459ff9790caefb93ecf1476b2c87f6cd962888b3/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f4b9d39b2cf40d297895abe54368f0a4d61070e8a92b08adb9c814f89d50e63", size = 1141723, upload-time = "2026-07-17T19:01:02.59Z" }, + { url = "https://files.pythonhosted.org/packages/bc/97/077a6c6b3b3a90a1c7ad7fb8d44bdc3f26a163dc93b3b866ff5e8e8a1856/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:936c6d100db849bffc82bcc35b518cadc8045dcb69ce901d80ca1c9fd56847bb", size = 919340, upload-time = "2026-07-17T19:01:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/680c226c91e66317e8d40b019638bd9c7b89b9bcfd9d9d2ed84629947502/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9761242122f8849ad804b68300497600139175dfffef98662af89a924fc39f", size = 934725, upload-time = "2026-07-17T19:01:06.405Z" }, + { url = "https://files.pythonhosted.org/packages/33/78/a9bfbc48141c735f68fbf8414c106013bff6d0a760fe036c78e947455c83/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:0c6b94447c58edb69e75cda066978e00cbcc929754e7b2ff8866b0ad289f9e4b", size = 900545, upload-time = "2026-07-17T19:01:08.377Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a8/48a27d195109f86097f44a1e545e92cdbe2ce148c34ed87e17aa9fb2cb8f/rignore-0.8.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bc8c586308ecd6551f6eabaf0bf7ca2d413ebf226d8b07ae1fbfaf7369862c40", size = 970925, upload-time = "2026-07-17T19:01:10.351Z" }, + { url = "https://files.pythonhosted.org/packages/89/02/55474346b60d3edbf011fd6e5f69e8894fb92f38c35c98110214f0412914/rignore-0.8.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:5414c445d990b7ebb68a58e54432822eb3bd9d52392bb5058d4c940d432737b1", size = 1066902, upload-time = "2026-07-17T19:01:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/d2/54/6dc9378e0575e6f9284dfc3f3a0ec1038821899ed4ed6832544abce47605/rignore-0.8.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:816984fded6e982823bf5d2ff0dd9d92491b41263a0ea494eca593a9db8f37da", size = 1137571, upload-time = "2026-07-17T19:01:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3b/5f59531a95890d0f60b4c77d818d96630e1b186617af91b0f0db1a998ccc/rignore-0.8.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:7ca95b54bec3485fb135aefb16a7a06354ec642790aeca5f2cefa0811f171452", size = 1147236, upload-time = "2026-07-17T19:01:17.376Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/52a185100a3a770cd6b6bffbbc2f632ca2387a138fa63da3e81cc22317d9/rignore-0.8.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:cacbd18af64f85fb52f84831856304cdd2d6e3e7926742b9d025782272325a08", size = 1145386, upload-time = "2026-07-17T19:01:19.335Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, +] + +[[package]] +name = "sacrebleu" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "lxml" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "portalocker" }, + { name = "regex" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ed/d7acddcff74d690c56fe26a1f7828bdde548262828d0743414ea916c40c1/sacrebleu-2.6.0.tar.gz", hash = "sha256:91499b6cd46138d95154fff1e863c2f9be57e82f0c719d8dd718d0006cf6c566", size = 1893419, upload-time = "2026-01-12T17:17:20.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f2/6c90ccf3ad1d09a7d662a405b274f3c93b92df59c8d6a025d26aaf34d302/sacrebleu-2.6.0-py3-none-any.whl", hash = "sha256:3edc1531575cfe4ad04ce53491a9307e234af1c3f805a1f491cbec844229a8a8", size = 100785, upload-time = "2026-01-12T17:17:18.868Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "lazy-loader", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "lazy-loader", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2026.7.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/1b/e6c69e4c2026ed575d68dda2847a404468ca7b5fa684bb0b19f71d82d29d/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc7b0b1da20f856bfac5f84b2673fe534b167e41980b27442ca8f78c2b7eb77e", size = 2180607, upload-time = "2026-07-12T08:38:01.018Z" }, + { url = "https://files.pythonhosted.org/packages/36/5a/2a1d84c87dc075d4f8cf1a2470a95399e59834e219ffb5f4285533e750d0/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b2db2056c97224e122054fd794543cde5d24b7cae28424f6e3eb79bbe08e42b", size = 1437502, upload-time = "2026-07-12T08:38:02.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3d43a75dd5a22503ca5074d0d37707cabb2e4a71b4bc6e6c61be3643cc7a/sentencepiece-0.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f1f61592e7cabd45d49ce8cc0ef42ca655c091e037153754fb3fa59725b5914", size = 1345667, upload-time = "2026-07-12T08:38:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/a69a8cc896e7de3fe2061b08c2f33e28656f243bed8af6a2df9f5d8c3124/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c798f0b327bac10dc95cdac77b9a197ab2bd7dd1e60ebd7586a12d918d4be711", size = 1322864, upload-time = "2026-07-12T08:38:06.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/79/dd1836df32971d4eb14ff5cb4a8b3fe4419adbeada8e81d09dc53c5c0ef0/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44284adc6fbe9d5bdd480541431a3d93f674fa44736714d3ad4bcee8283ace7d", size = 1392757, upload-time = "2026-07-12T08:38:08.559Z" }, + { url = "https://files.pythonhosted.org/packages/26/83/c3547715c29b7e4c84a180a240267f7685dde6f9b981396f16b95405ec9d/sentencepiece-0.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:1120e0791540615e650b2e9bea835bf38a7362455d8ab62dee7968219c2d79a0", size = 1245044, upload-time = "2026-07-12T08:38:10.21Z" }, + { url = "https://files.pythonhosted.org/packages/1f/55/7da03b35582a4eb276f99051109f3e3e8f176835b6d6837422e4c3a013dd/sentencepiece-0.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:524e2a85c028a0d2f9935191fa751e5ef9d9bcc39616f70ab14b28d0369c9936", size = 1190467, upload-time = "2026-07-12T08:38:12.07Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/f23a2efaa0210b883574001b88fa64e499f798f0848a0b610fb9b384d162/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69e9dc8078e128286ed3b975e37c837ba96e215a50c3ef9f3f8b7ab9e5a832a0", size = 2184255, upload-time = "2026-07-12T08:38:14.855Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/1ee0ccb772d71e822f625d6cb5f0ea825835e877f28a9ef299a1291df19e/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6dd76f3e5c8b2eb8a3a3efee787bbf5b9a66e52a048fe09cab85eca33fec6790", size = 1438545, upload-time = "2026-07-12T08:38:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/2a/92/3a6ea4a2c6dd9e7062698a5a33534ca0e20844883338ae9c6b9c122c1a9f/sentencepiece-0.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:443ac618c7a2a1377cf5c82581fbb849591d14e656d5e5a3e4682d4e36a34e4e", size = 1346997, upload-time = "2026-07-12T08:38:18.499Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3a/7839048997c7bc0c34c57526f539f835e20c7a57dc2a99f99579b11cdbef/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e2aae42960392d6dcb9a72d8e1e65a97294c965071b43c7b3429a42f350250e", size = 1324282, upload-time = "2026-07-12T08:38:20.342Z" }, + { url = "https://files.pythonhosted.org/packages/06/5f/9117bf854aef817ad0d0ee9310eed0308a7e529e7eaf2e80ad9cd281ef82/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1416b92f2f010333786fe6306ed2631121d5ea492219b0841e967b6765e64107", size = 1394242, upload-time = "2026-07-12T08:38:22.976Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/9e2569867e3dcff7ad6d89642a9615b9801b5cd698abe7df3b490361f66e/sentencepiece-0.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d4ca6f4d06df7f0ccab6fe4f49c8a712c8c8b6847b4f0af9a0e1dbb0e0337e", size = 1246268, upload-time = "2026-07-12T08:38:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/96/c9/5d781d4ef1124564a45c98b9ff25d531c10cdf568ec6314a2d1946f9251c/sentencepiece-0.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:252908153eeec06c3ca3a32077e64a49d572e3d89881475b4e0f02d99d9fcc7c", size = 1190702, upload-time = "2026-07-12T08:38:26.789Z" }, + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384, upload-time = "2026-07-12T08:38:28.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553, upload-time = "2026-07-12T08:38:30.552Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176, upload-time = "2026-07-12T08:38:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200, upload-time = "2026-07-12T08:38:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736, upload-time = "2026-07-12T08:38:35.771Z" }, + { url = "https://files.pythonhosted.org/packages/8d/11/753fca2e6b109be3ab7867abf357dfe48677fe726ae5a5363d0b54ca9450/sentencepiece-0.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383", size = 1248030, upload-time = "2026-07-12T08:38:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0a/70efbe861ca182d7d4b6e1a20f58e043400848fa9f2915229f082e221648/sentencepiece-0.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d", size = 1187325, upload-time = "2026-07-12T08:38:39.348Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a3/b3b05095c174d6e80d37d5ddc2f57c2c56237333e7bbd6079cf3243c2a8a/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8", size = 2188346, upload-time = "2026-07-12T08:38:41.089Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/72ebc4acb10a06bcf7503fbc6091c8f5db68300f6aac4356c09e6c76e0e1/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c", size = 1441434, upload-time = "2026-07-12T08:38:42.56Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/f9ea1a6844b4fa5dfe2312095cd866a1f724cd0905054ab9d5991778ba50/sentencepiece-0.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a", size = 1347267, upload-time = "2026-07-12T08:38:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/32/4f/31c1073314ad94466bca37d29581761d70110237ee3d46b0efece59a8c1e/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0", size = 1324980, upload-time = "2026-07-12T08:38:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/59/b4/a0356fa04d6a14337a6e0e443556785a0422c53ec58baae6b9568120eb0f/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb", size = 1397593, upload-time = "2026-07-12T08:38:48.302Z" }, + { url = "https://files.pythonhosted.org/packages/09/fa/d2d6369257fd2f0de616b1c7110b73fab409ef61b14f1b9e0010ed325914/sentencepiece-0.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9", size = 1247987, upload-time = "2026-07-12T08:38:50.15Z" }, + { url = "https://files.pythonhosted.org/packages/17/ee/2bb594da6fd95e32f29057f1aa7fa996701b8980090923c2d8711fdc0a24/sentencepiece-0.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91", size = 1187250, upload-time = "2026-07-12T08:38:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/58/9c/dfc82846460e7a712310f5613f23d8b553cabb4e2e648663c11d8382af56/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78", size = 2223080, upload-time = "2026-07-12T08:38:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/3ff12cebe6d31662d9ceeabfb282de20bd0d6098fa282b4a3b8305abc7e8/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563", size = 1458511, upload-time = "2026-07-12T08:38:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/5a/16d51d05360be4cee3ebfe4837c184054c4eed16cabaeb3b039524e9a000/sentencepiece-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5", size = 1361138, upload-time = "2026-07-12T08:38:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/0f/af/c30ee2a9f99d51db9844acaa8fa0b611a97c2fa7116646fa43db3300b187/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d", size = 1328625, upload-time = "2026-07-12T08:39:00.849Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1a/4c6b39d03f5ba8439509adbd5a23c9538088a3cb679e7a47b911e8442bc6/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b", size = 1398595, upload-time = "2026-07-12T08:39:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9eedddcec1fd57bc70200fa3ebf792d18fa63527a5369581cd416c81f97f/sentencepiece-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53", size = 1259346, upload-time = "2026-07-12T08:39:04.559Z" }, + { url = "https://files.pythonhosted.org/packages/41/15/7e74c8533848866ff560b29f7d8719921b76c4ec7149592d6d28e0deee75/sentencepiece-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd", size = 1196596, upload-time = "2026-07-12T08:39:06.454Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/f5df63edb6bcb46c1343cfa5d9192d73a4eb61af2e800d9402efff387523/sentencepiece-0.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c62bd361cec1f5b556eb8210264ecfff37486cd990c3386cc00310f26c54090a", size = 2190240, upload-time = "2026-07-12T08:39:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/52/0a/095d183b453b2a2e20b016829029c58eca90adc1c9911113e5d26fff45ed/sentencepiece-0.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:46ba07b543add034de0ff47ac5f907e9a06682f91d85121a972764628933be6b", size = 1442220, upload-time = "2026-07-12T08:39:09.91Z" }, + { url = "https://files.pythonhosted.org/packages/d1/18/823954c9c90e74eba09fb96752dc37a5555df00d69866cb9406d1725dc7e/sentencepiece-0.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79bac5a251f23a7341e28fda9ce0d5319edf45328239ce037c0682936f137906", size = 1348056, upload-time = "2026-07-12T08:39:11.744Z" }, + { url = "https://files.pythonhosted.org/packages/10/ca/1b6c251321901cbf8a2d2e48b8b70eb82a449011b766af52a228d0a90b6b/sentencepiece-0.2.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1402d8ee36f0d851cea8eee4dbb85fea14643b7503cf4d00d102eec0fe3ca719", size = 1325463, upload-time = "2026-07-12T08:39:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/24/b3/718847349da7b25c8220ed86d85b89080af94740b2d87a59198104ae5c51/sentencepiece-0.2.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d44b20234905ff022b7d535f79d1f823ad7670c9851cc4f03cdc34787cdb3ab", size = 1398138, upload-time = "2026-07-12T08:39:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/33/fe/4906f12c458274edd96387e4baaad7c6f064a2b7c11a1cc2401c8a7bd483/sentencepiece-0.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:63250cfab8b80a1ef82a614eb2b3cadfec2c405f870cedc139d08e2f063eb708", size = 1356144, upload-time = "2026-07-12T08:39:17.313Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/22f89b6542aba400b0007cf0b1697cc3f99be8fb682fdb4c05eec450e33f/sentencepiece-0.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:65d84ec36888de4a848eee5f910e67fbc79b064685ef1e10a502e14520ead9c9", size = 1294351, upload-time = "2026-07-12T08:39:18.967Z" }, + { url = "https://files.pythonhosted.org/packages/84/c4/7afe8c2315b76e46818851a057e50a378a0382aa00b970a1fa444181b6f6/sentencepiece-0.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d254c98ca6387655400b3959c33c83efd807f5edeb608e3aca45800ceaa77151", size = 2223281, upload-time = "2026-07-12T08:39:20.978Z" }, + { url = "https://files.pythonhosted.org/packages/98/42/fb678e472c554ef086be6375d20060ca610a2c4218854d4c091001fc6f91/sentencepiece-0.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3fd9ce2ab4460c713cfdeb4aca693ca6732a11538e05fb332d5af42e3d7fde25", size = 1458779, upload-time = "2026-07-12T08:39:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/78/52/ffe402b13bce1889228a98dc6cd86ae8afac1112362236be3468be784441/sentencepiece-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7fc14c1585139fa6b68775e616a6b90cf622ebf219f9558c0aeaf5d253ee6c9b", size = 1361736, upload-time = "2026-07-12T08:39:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/78/4a/2288f60e7283583ec0a0f16e72f9c8e68557d7e7a4b585d2cda4f9f47e64/sentencepiece-0.2.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df88b0c34f2fa909d322f7b06b1398e1e81af4b2f42a7b8e3556f928b25d1811", size = 1328155, upload-time = "2026-07-12T08:39:26.422Z" }, + { url = "https://files.pythonhosted.org/packages/26/31/5dd6882ebe899f741a5cfe40ff56c6efc06bc26ee287abdb723b671f409c/sentencepiece-0.2.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f5851441ab1ef8634963a5100b733a8bbeefe623e0c5c005b1f1f3880e574cf", size = 1398307, upload-time = "2026-07-12T08:39:28.637Z" }, + { url = "https://files.pythonhosted.org/packages/da/05/7d7780fa63f4b8c1821953b916e25f89ae8f14d4da6ba91e10f6d06dc2b4/sentencepiece-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:046b15ea22d8042e2e173561d464ec3b64a9c2081324df70ebce7bf7ebb3e497", size = 1367133, upload-time = "2026-07-12T08:39:30.546Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/70007fef3f818c688de4a730f98024a671599ab67f20270f8efb03d69dcc/sentencepiece-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fa9f5ef0e2a82233dd0b8b32ea3f5710e0c44afbc07ed3620219f32601e56090", size = 1302760, upload-time = "2026-07-12T08:39:32.457Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.66.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/6f/d59cad0889d15fde85254cf58e701484de3f3f0406003b3197746910b19b/sentry_sdk-2.66.1.tar.gz", hash = "sha256:f882fb08710c5f8bfc603aafa3e901b384009a19cc3f76a572b863392ee81cdc", size = 940543, upload-time = "2026-07-22T12:26:54.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/d3/726bd88f0eece09ddf431bea4c9191c18e7a8d070b854eb0014d447712ee/sentry_sdk-2.66.1-py3-none-any.whl", hash = "sha256:86002793161d9a95ef04bdd8d442e9bfece5d989b755f05d6360215094a7aff6", size = 505555, upload-time = "2026-07-22T12:26:52.71Z" }, +] + +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + +[[package]] +name = "shap" +version = "0.49.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "numba" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "slicer" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/c6/9823a7f483aa9f3179fc359c10d22da9e418b1a7a3fc99a42b705d05e82a/shap-0.49.1.tar.gz", hash = "sha256:1114ecd804fff29f50d522ce6031082fcf42fe4a32fb1b5da233b2415d784c8c", size = 4084725, upload-time = "2025-10-14T10:04:49.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/a1/66b4f04995ee23ff8638c21294f1a3a6dc87397af54c87aeeb037500f71f/shap-0.49.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40140ec5d306719f89daee1df27805a71bcc1ac39630832455d316d0306d1283", size = 558950, upload-time = "2025-10-14T10:04:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/2142615fa5cc745fd66beb066d00db123cc86d614a31ca8029b29537a959/shap-0.49.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6e9977f1e0b6bba57967de600e8e6047b3e4643d06a4671f2dba1a97c1b5ab3e", size = 556605, upload-time = "2025-10-14T10:04:10.049Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/e28014ffc23f386da3d69abd978838e653fff5641831e5a34aade3f4dfe7/shap-0.49.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54ad4c38e6af56eaa1c892bb3af550a35df15ca0d27d2d41c1d1619ca6a2ba75", size = 1000329, upload-time = "2025-10-14T10:04:11.359Z" }, + { url = "https://files.pythonhosted.org/packages/bd/09/734325f0a9ab9d3dfa5c0908a927027b3d95b3f6929bb62d88e840b85abf/shap-0.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcd832e97038648ba89f659863322d5cd3ea0815e18c36dd48cd7ae1ca9f264b", size = 1000713, upload-time = "2025-10-14T10:04:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a2/0518acabb104e21fecda65b0202e41edd06637c44dac15e2197e7d13a002/shap-0.49.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fc2e864908277dca2b1d9c59a18b3f31576b985bd024f39b0c3cb7e2c7441db", size = 2065477, upload-time = "2025-10-14T10:04:13.874Z" }, + { url = "https://files.pythonhosted.org/packages/af/e1/3d52717b617b9ad1e4d0c9634d3b7c52a913540fde27c4b4663a7ee76b87/shap-0.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:4f5bec3d061b4f4889e1ac4e9b676aede2875778ff44b9d5f5a844cbe6788fd2", size = 547034, upload-time = "2025-10-14T10:04:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/1d/08/d433b7d18a8b51a7d10477120f78877d806d2eb86283cb1661318d865f3d/shap-0.49.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e208a0129c721bd0eba6268a9ffac4610dbc8a833d07d2ad9f39541bb737f06", size = 558742, upload-time = "2025-10-14T10:04:17.45Z" }, + { url = "https://files.pythonhosted.org/packages/c2/35/72929fdad25e055aff9dfbeb48c044682fc3b815d90cee4036b90bd65f4c/shap-0.49.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b878470bdf6800069c25d2a8598eb0548aa1e6826becd39cca253521cc14866", size = 556486, upload-time = "2025-10-14T10:04:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/02/be/d92623be2c584784e99a8eb9a6cd02263b4eb363c9e49fa14c20f824bcbb/shap-0.49.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118577d40c53f005268024e59f6a10cbcafbb6d03b3d97dce7c0c7510190ebaa", size = 1025978, upload-time = "2025-10-14T10:04:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/e4079b5de26a8269121ce38125e130c147dac7b59611e0bd94be10f9444e/shap-0.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f424465699aa2dda8057656c6b6d3cb927cf29b054c5bb01cfffcb9efa5dbf98", size = 1027831, upload-time = "2025-10-14T10:04:21.666Z" }, + { url = "https://files.pythonhosted.org/packages/49/ff/e22e1d899ed56384a2395d6121d6e21833c518c01c5b6c52fce3c0b0cbab/shap-0.49.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d505834fdf2a159e88b1dcdeddfd79f101fd789ba89d589faf0aaec060c0bad9", size = 2092627, upload-time = "2025-10-14T10:04:22.894Z" }, + { url = "https://files.pythonhosted.org/packages/17/48/bbcd638a391ac0fb30033398a3cca60ba5c36941d962dd74958e67069108/shap-0.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:897c7e6fa98d66482282c8f898c97ade181d714ecaf581da0dab5c49adb9f62c", size = 546845, upload-time = "2025-10-14T10:04:24.238Z" }, + { url = "https://files.pythonhosted.org/packages/92/7a/ccecf7a9158baa10bdc5146907c72dd5f85c762cb5f16cdc74d15cebb8a1/shap-0.49.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c652dc77f1fffe73f5a3def3356c5090e2e6401c261e4fe5329d83cb6251e772", size = 559663, upload-time = "2025-10-14T10:04:25.412Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c6/c43382d6c891fcf067d0a9f6d954351e3c7d330f4328c5816769b796aa27/shap-0.49.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c23f1493205e648634680c8974e82e7f4b2e96ae3a7eca2251680172bd197ae9", size = 556265, upload-time = "2025-10-14T10:04:27.098Z" }, + { url = "https://files.pythonhosted.org/packages/c0/71/f7db7a5a2cedaa3ac52f58f453172d613be041bedd9509ce5b5cba2096a6/shap-0.49.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41147740c42821023e1b60185ce8be989656ccac266cc9490d7a8e3ad53c556a", size = 1022419, upload-time = "2025-10-14T10:04:28.793Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a4/96ca9a69dd669ff835ddef875c5dd8e07599103769417d3e9051fd97d470/shap-0.49.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef9952929d4a7e6763d2716938067bdad762217e3afb46cabfc15a62c012b364", size = 1027074, upload-time = "2025-10-14T10:04:30.2Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9a/89ed1ac8beffe8ff8e09c12cb351bc3c79ddaadcc47ca6ee434d76e464d7/shap-0.49.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e823417eb0a01947cd9bd763bef2e534c5aef7a7c2952b1badfa969c7d59d3b3", size = 2088172, upload-time = "2025-10-14T10:04:31.725Z" }, + { url = "https://files.pythonhosted.org/packages/4a/28/11422c1c3aa022a06e76cbfa3267e1750cedc00c1e02ef1ccae9c88cd6f4/shap-0.49.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb28043decfec3f35f795421eb5a81545f629b7f60bbf7449cd2843a7f1c8cc6", size = 548036, upload-time = "2025-10-14T10:04:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5c/030bbfa19605ca4ad66a753d55e76aee5093be6748a6d33eda89e5613995/shap-0.49.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:333cd8e8c427badda92d5ada9e7aad1e3e1e8e7e0398da51a18b7ffb03514e45", size = 558604, upload-time = "2025-10-14T10:04:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7f/7e7b78e9fac6f891096fb6a59a6d4db23243b0af2369ae54e161f513c485/shap-0.49.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4faf61560f73a66f4f26bc027c91f8939201979c4db24949dca305ba0a2ad36", size = 555311, upload-time = "2025-10-14T10:04:35.582Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/25283a0f8c30deaf897b89a0dbfd490d330f6fc68caa6f19db6e130832e9/shap-0.49.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b440da658d9aee7711bf642c9b4826d81f588fb478cd9e90c068646e90f56669", size = 1016897, upload-time = "2025-10-14T10:04:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/91/a63e563f3dc8e134db12dd155a1a6ed5e0649f79fc8ac651aac1088e8652/shap-0.49.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8dfa5654eccf4d13dcb262a10314a4e0eb1060db842b2ef31e9fb0038168bc1", size = 1022476, upload-time = "2025-10-14T10:04:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/15/a2/89303c1f7eb206658bf9ec974dc6e69b0a6bd309cf5de0cfa8f92f5a8eb3/shap-0.49.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed3080030a6000d3737841c5770ed555b8a922b794fa0ba5aae1e45655eda1fa", size = 2087940, upload-time = "2025-10-14T10:04:39.497Z" }, + { url = "https://files.pythonhosted.org/packages/84/bd/0b9b3e19b9b8cda51463f8a749dc354eb9c87f42eddcbfdf742dceb3746b/shap-0.49.1-cp313-cp313-win_amd64.whl", hash = "sha256:6af779344c23b12a47063aab7fc135fefbdb5849233c1813f11dd8cf2fc73bea", size = 547806, upload-time = "2025-10-14T10:04:40.712Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sklearn-compat" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "scikit-learn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/7e/302cb51f8735bad67f5ce088d027a1e299789d8555967c9642656fa36da0/sklearn_compat-0.1.6.tar.gz", hash = "sha256:8fd4731b4f709b66641b8f49c954dafec7e3b60afc48f2cfd298356c713277c6", size = 178018, upload-time = "2026-06-07T19:00:28.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/20/47a7e947757008be1b77f1c6a6861d26a84ef4b4ea3e6ebf4eff24f24d5d/sklearn_compat-0.1.6-py3-none-any.whl", hash = "sha256:b555db6c09d21eb50ee4a767dc08478a865f33f0e42b3ff8fc33f33c616bd7c1", size = 22868, upload-time = "2026-06-07T19:00:27.242Z" }, +] + +[[package]] +name = "slicer" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f9/b4bce2825b39b57760b361e6131a3dacee3d8951c58cb97ad120abb90317/slicer-0.0.8.tar.gz", hash = "sha256:2e7553af73f0c0c2d355f4afcc3ecf97c6f2156fcf4593955c3f56cf6c4d6eb7", size = 14894, upload-time = "2024-03-09T23:35:26.826Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/81/9ef641ff4e12cbcca30e54e72fb0951a2ba195d0cda0ba4100e532d929db/slicer-0.0.8-py3-none-any.whl", hash = "sha256:6c206258543aecd010d497dc2eca9d2805860a0b3758673903456b7df7934dc3", size = 15251, upload-time = "2024-03-09T07:03:07.708Z" }, +] + +[[package]] +name = "smart-open" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/53/9c513747547fd595d5c143259129ea8b9c3ea2f6b7bb9dcea2b1966ded3c/smart_open-8.0.1.tar.gz", hash = "sha256:18b1c4496003c6902be17c15f032b5c319f307c89c6ae9e6b028b508bed8b2cf", size = 61882, upload-time = "2026-07-15T13:56:10.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/96/325b8c507ccecc50421fecc0345a502ee6e4a44785af3c4e6ecbadad624a/smart_open-8.0.1-py3-none-any.whl", hash = "sha256:3e97f90e92a952cb57863dfe132082c400a52eeeb27c067692fb51dbcc5b0089", size = 73504, upload-time = "2026-07-15T13:56:09.033Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version == '3.11.*' and sys_platform == 'win32') or (python_full_version != '3.11.*' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version >= '3.12' and sys_platform == 'win32') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" }, + { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" }, + { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + +[[package]] +name = "sqlalchemy-stubs" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/60/db082788267740b17eac2c00666bbea1c8c5a94b569e8b1ea76b0cf42d57/sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae", size = 70682, upload-time = "2021-01-12T14:02:04.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5", size = 116067, upload-time = "2021-01-12T14:02:02.723Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "stevedore" +version = "5.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, +] + +[[package]] +name = "stevedore" +version = "5.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/dd/04d56c2a5232358df41f3d0f0e31833d378b6c8ed7803a6b1b7867b0eba6/stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c", size = 514850, upload-time = "2026-07-02T11:38:08.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z" }, +] + +[[package]] +name = "streaming-form-data" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "smart-open" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/fd/d49f3b4e6258e865566fd8aa3da9966f47ca5a7d7fd8ca181f8209010605/streaming_form_data-2.1.0.tar.gz", hash = "sha256:2c5c81fc9c451ea133083bc6da959f87e9b91fba3effe99411f1f90461ea7c5b", size = 150867, upload-time = "2026-06-10T19:35:59.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/18/9bf597fd18a2a16c24981afa6ded6dde18d329c5959bf2d060d695d9a144/streaming_form_data-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7efb67a2bf91419468f8c84d89fc997f0443ca7640efd2e4ebf31656253627a6", size = 221853, upload-time = "2026-06-10T19:35:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/b71dd002d62a80e3f4ebcd6dba42a07ee180b7d800625e4e101b227b5013/streaming_form_data-2.1.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2269ddc234673d8b99863d0203f12d6127eda96d4c4155ae5728b9862f05c5e", size = 626881, upload-time = "2026-06-10T19:35:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/a9/77/421e4437fb8eaee7c8396ea5a5d2d54ff22b96279dbae4cb251539824933/streaming_form_data-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:233026ea931b4043ddec64dc774753073a03caee5f76be450ceb73e15c31d016", size = 617009, upload-time = "2026-06-10T19:35:39.308Z" }, + { url = "https://files.pythonhosted.org/packages/b3/96/e94f0ace935a23011ff44e3d0f9b88950159fd02b45a1528c3b57ef73876/streaming_form_data-2.1.0-cp310-cp310-win32.whl", hash = "sha256:0ef2778e554bc7db29f6493d229d2eea647f5df8b104cf017516a9bbf5e869fc", size = 198327, upload-time = "2026-06-10T19:35:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/abd0b2b1772a91bfb979d52d5ddd5a70a1ed62506b0cd23c8450dd2c1f4d/streaming_form_data-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5b46ea82530e4f9be08396b388e38e3b89f26345a19e2f84ade0302d92cc5dc", size = 208117, upload-time = "2026-06-10T19:35:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/90/9a/9239a3e8c6fb10e0367c3aec387eed816cc9fe411a43cd998203d269e3f5/streaming_form_data-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a94d5eb98399fa9bd69741a4fb784d92ea8a774850e099a4bb6bb5812d773ed7", size = 223149, upload-time = "2026-06-10T19:35:42.814Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/0e3490b9ff2dc14dbff8baacf1c23c15f24f8ad3434327022b5f59e50e2a/streaming_form_data-2.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:97934de76c520182e8536748c8f07544d646777174a41215ee15c3eeca0de479", size = 664319, upload-time = "2026-06-10T19:35:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/ef/69/e50cd2c4fc8e216d7a6a073eea4239f744db8bf556b93fd8671b23e47358/streaming_form_data-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab74a306ac7db0fc8a4539c62b55db0488d2f81928648a66ccfa0770051cc4f7", size = 657767, upload-time = "2026-06-10T19:35:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/55/b5/2bb7a12abdd81bccd311a12fbabe8c715774e2d99a12cedf0c294179154b/streaming_form_data-2.1.0-cp311-cp311-win32.whl", hash = "sha256:c9d17aaae0a171f74611cd2bead3dc39bf3cd5f02887af67aa8d4da5b3647022", size = 198458, upload-time = "2026-06-10T19:35:46.198Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/2cd860cec26b65d1d65a1e371cb1fb094aa15a0cee6235f996d32c49fe22/streaming_form_data-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:582912c9f488569ec8d7930d73abedbeb96dd74ea447b7d6fa4691e730276884", size = 208623, upload-time = "2026-06-10T19:35:47.158Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b2/3123dc2b39ff69a5cf7bea5fb2a0a7aa2b41c4c43d3c489eada7cc249873/streaming_form_data-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:109390324580f0bab0777f9f347843c29895aa78028aed86e5931158008ef369", size = 223269, upload-time = "2026-06-10T19:35:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/09/31/335732ff6f370eeb42391505a2d08c32ec5381b846cd619a4e58b2cbdad2/streaming_form_data-2.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c10cc7dc41c79ea270ad93d1f1dc982750eb5b13f719d9a978f125c0b3b86371", size = 664217, upload-time = "2026-06-10T19:35:49.445Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/7c69ce4977a81a4e02221abd73c7de8a2e2f34a53987f64c041d2920d706/streaming_form_data-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:055d40c7a03d56de9751167a95b62176f9b2283c808d4818f78b0ae4b872d166", size = 651437, upload-time = "2026-06-10T19:35:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0f/80db74a30563758768550276cb6b07d6e9248c24176cfb1f021abf47860c/streaming_form_data-2.1.0-cp312-cp312-win32.whl", hash = "sha256:a08266f5328071d2b57c43448cacefbaf80ab5e33e3020401f064f688e748dbc", size = 198099, upload-time = "2026-06-10T19:35:51.647Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a5/53c01f6d0474d53bfdb9f32ffe6946101b499f6c698dd61ac560eace72be/streaming_form_data-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:76c36952a7399167984e0146b1dcd50fcd58e4adf58c28cb8150bd2973c5f8d5", size = 208512, upload-time = "2026-06-10T19:35:52.683Z" }, + { url = "https://files.pythonhosted.org/packages/13/4b/6da0657b08df77c9b3399273976e7bde90b9156254bf6237d0d84dd440bf/streaming_form_data-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7841684f9ac6476cfb0288ab670c2b08b1f1a06ddcac67b851843c5e53b27b7", size = 222265, upload-time = "2026-06-10T19:35:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b4/0db7ffb320710b851ec290eedbbc5875a3e2b82fae3418632ac860c25b31/streaming_form_data-2.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a917c93e45df1e7296964f46a98ef4a73ab477c10cebe1abb2667c90983f4d73", size = 660321, upload-time = "2026-06-10T19:35:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/1f/c8cffb5d4ce2d9fb02bd0190f66b682405e972e09a22517dd11a0f08f6bf/streaming_form_data-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28209064b60d86ff065b2a0776adccebd849beb2507e7f9cb995597ae2d30980", size = 647626, upload-time = "2026-06-10T19:35:55.967Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cb/1ea4254bc0cf107a0d853ccb3aca5f2db41f238de2e8b0dc7b55b51d114a/streaming_form_data-2.1.0-cp313-cp313-win32.whl", hash = "sha256:0d92b76a51ef0621b37c437deae8641589e21ff3b132a407b146753a7b7f6576", size = 197919, upload-time = "2026-06-10T19:35:57.06Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/77b35bfca81c6cc4546c35b38998c5fde2d5783e3b3a14ceebced1415ed9/streaming_form_data-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:2d688a0205d44441fdd38010f84b32a29668d81537909b2832d0ecdf02b43a2d", size = 207883, upload-time = "2026-06-10T19:35:58.091Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tifffile" +version = "2025.5.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.7.14" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/2f/e5fe51c8f782241d86fdf7251594b195f0d6c2fcf9d389079de212599246/tifffile-2026.7.14.tar.gz", hash = "sha256:ce2703e5ef22c868f1528d5f5b4ef75eefb019cf628a1c9ec0d17e0afeca8ef5", size = 437660, upload-time = "2026-07-14T23:41:31.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/e8/d381de4a3cc4e3682cba0338f43250893508aad0b310af1d0635f7b04413/tifffile-2026.7.14-py3-none-any.whl", hash = "sha256:4eb20372e76edf2c9fed922b1e3a0a0567be3560bd2008336115763bb1f3c034", size = 270614, upload-time = "2026-07-14T23:41:30.078Z" }, +] + +[[package]] +name = "timm" +version = "1.0.28" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/03/e41389ac641747bfec48d016fde8be1eade1901e6f2c1aedcb0c8cb4b5d9/timm-1.0.28.tar.gz", hash = "sha256:3789d313fdd5541a327b60180d70dbb4bdec73db8ff0655e413db3c3d134a9a4", size = 2451413, upload-time = "2026-07-11T17:24:32.615Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/76/de1bfac17d183c49c6d0887903d3064ced51cf1d9ba7a8d611c1a8808c4f/timm-1.0.28-py3-none-any.whl", hash = "sha256:e577b88da96b3a722ea5e2f042455ce6f715d398304d8e63b17d126ed7d89968", size = 2597944, upload-time = "2026-07-11T17:24:30.869Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, +] + +[[package]] +name = "torch" +version = "2.11.0+cu128" +source = { registry = "https://download.pytorch.org/whl/cu128" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-bindings", version = "12.9.7", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cuda-toolkit", version = "12.8.1", source = { registry = "https://pypi.org/simple" }, extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "fsspec", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "jinja2", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cudnn-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparselt-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvshmem-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "sympy", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "triton", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "extra == 'extra-6-dashai-cuda'" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5ac6e34681d5a0e527edb741b38254899cd03087a7dd7e841791a4ee0a5e7011", upload-time = "2026-04-27T17:32:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:72d53f3176a69cc20710c4ecb95f7dc4c6ba10c4e4eda45b8396ee79ee40f75a", upload-time = "2026-04-27T17:33:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:7c792fe95ad5edaf622cf9e4f5573f5aecf2bc0654c7e866eda6134088f95d72", upload-time = "2026-04-27T17:34:55Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d76f08e212285bd84c4c5a3472417f8eb4ee72e4067a604f7508dbfa2119771f", upload-time = "2026-04-27T17:36:45Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c9a7ca4c74fae10a58e6175b4b2cea953f9322bb6562bbf339ad6a05f52190ad", upload-time = "2026-04-27T17:37:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-win_amd64.whl", hash = "sha256:90ef0c2454e5296a9fb021ddd42252e4ce1abe2c0a4988a173ef90a6cded0bf5", upload-time = "2026-04-27T17:39:29Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9c8f38efee365cb9d334de8a83ce52fc7e5fc9e5a7b0853285efa1b69e00b0f2", upload-time = "2026-04-27T17:41:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d252cf975fb18c94a85336323ad425f473df56dab35a44b00399bd70c7a3b997", upload-time = "2026-04-27T17:42:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:7c78215c3af4f62e63f2b2e360f1722fc719b0853c7ac22666483d9810613a4c", upload-time = "2026-04-27T17:43:49Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7db3580106bba044da5b8950f3fb8fe5f31999eaab3f6a3aa2ac5d202c3684d2", upload-time = "2026-04-27T17:45:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:db964b33c55035a72ab3e2162287af8f1cc276039c65d015740cc88c26dcedf7", upload-time = "2026-04-27T17:46:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:6f367e62fd81b75cdf23ca4b75ced834d2db2cf98d1588ac935bde345de9de23", upload-time = "2026-04-27T17:48:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd1cf1005c5fe419194ee294b7b584ba5ad0f2fb1778b3fe5a7b9c3f4617ddbc", upload-time = "2026-04-27T17:50:01Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:74b628dbc71603977b09f4e140792c6e997081a35ef3421555f3f6e201b81210", upload-time = "2026-04-27T17:50:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:c2a5984deba8e001d166bf9cb83b8351f63a28b009e1a2fa0e4bbf08c90b259b", upload-time = "2026-04-27T17:52:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:baa52f7b8a53cab16587b10f1c27d1000ca033f97236878b685b75d5a1b92408", upload-time = "2026-04-27T17:54:24Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d389a850677f0d24dafae1573644034428d8d3b9c80b51d55ba62fed7e6c8777", upload-time = "2026-04-27T17:55:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:d6c21797ff75271b4fbdd905e2d703be4ecea5ea5bbdde4d1c201e9c71bc411d", upload-time = "2026-04-27T17:56:46Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:06849e9311dbb0617c97557d9c26c99a9e1c4f2ac9cb8e9b6d9b420d522acb91", upload-time = "2026-04-27T17:58:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:169a9987e1f84f0c5eee07544b3a34827a163ac9180e23abf0c3548f1335762c", upload-time = "2026-04-27T17:59:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:d86c125d720c2c368c53bd1a4ef062916d91fa965c10448c74c78b5d039faf2d", upload-time = "2026-04-27T18:01:14Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fsspec", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sympy", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", upload-time = "2026-07-08T12:26:07Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", upload-time = "2026-07-08T12:26:13Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", upload-time = "2026-07-08T12:26:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", upload-time = "2026-07-08T12:26:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", upload-time = "2026-07-08T12:26:28Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", upload-time = "2026-07-08T12:26:33Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-bindings", version = "13.3.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.15' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cuda-toolkit", version = "13.0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "fsspec", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cudnn-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparselt-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvshmem-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "sympy", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "triton", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.15' and sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/e7/19894fdb51c7dbaf94f5a79bb0871da0992e8e4241e579cb006da46d2e58/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", size = 111178962, upload-time = "2026-07-08T16:05:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5c/b1d5de470c54e339b30a92d96683a71bcebd78f5f2a7fc714cd6dc6bbd68/torch-2.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ab4b69f3ee03a62a002cfbf77b1ca5e88aceb4ea64cb4388bb28f638ddbb045", size = 427198333, upload-time = "2026-07-08T16:05:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/50/c0/68a84105e1fcb8970144b388ff3d3e5dc15a3be28c1e247841f7d7247e41/torch-2.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c78b7b4d04461855a764cf01bae9a462bb88bc93defcfa11235cbc8fdf3e12c4", size = 526555154, upload-time = "2026-07-08T16:05:06.507Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c9/0bb9d097b03cbaf96bb75b15e867347b8e41bfcdfe0539452d17d9e63993/torch-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:2bd30b6b730d987fa386ce3898933762c5cb8cc82eb0535211d787cc3ce2dfeb", size = 122015602, upload-time = "2026-07-08T16:05:45.25Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "fsspec", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "jinja2", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "sympy", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "typing-extensions", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp310-cp310-linux_s390x.whl", hash = "sha256:0555fde6108ca90247ae33d4e1237cbae475c86a223bb2f0f91d9addf1f611bd", upload-time = "2026-07-08T19:27:11Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:f028e428bddee95cdb86e2470254e95c9af629362488550c200ed4793125a817", upload-time = "2026-07-08T19:27:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f5cbb61180a9793d9e12fe115a2310d2600bd449dfb9a01ec5640e21359fa5ea", upload-time = "2026-07-08T19:27:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:3bbb357161e8db43ba7cdcc7e03561eba0c449392f2f27d3566887198fcb4ead", upload-time = "2026-07-08T19:27:43Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:6e9817dbdf5ea76789babd46e457eac5bf14ff566cf85f8addbfdff2d56601ce", upload-time = "2026-07-08T19:27:52Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:84453b69508ec79902f899c5ed9495acb9e2bbe9fda5f1d5d6f19e3c3842e1a7", upload-time = "2026-07-08T19:28:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6746dbcbeb526eb61330b76b41ff1b4eb848951103a892eeb080dfa2b264667b", upload-time = "2026-07-08T19:28:16Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:10717d8b3b67c45a4788bf7ffc0bab1ea1e5ebbedd24466be6100102d141fac1", upload-time = "2026-07-08T19:28:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp311-cp311-win_arm64.whl", hash = "sha256:2b3d093abd919ad934c43d47e73ba63ceba7cbd7269fc2e9c1e4fc29e8fe45fa", upload-time = "2026-07-08T19:28:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:ffadde149901c8afa138daa38d898264003cfcf1a3336ca5cd964b5af227d867", upload-time = "2026-07-08T19:28:41Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6f307c2c32d764ffc6ff6893b801fad6d4752f3e67966cb8abf1843427c02604", upload-time = "2026-07-08T19:28:51Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ca4a9394b0c771238a4f73590fdbbc4debad85ed0fa63d026ae1b085da7d6e2", upload-time = "2026-07-08T19:29:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:a8b450c1e58e5800e5b4691dac412f8d2d65a1dc3298166f91596603a3531e6f", upload-time = "2026-07-08T19:29:15Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:fa0762705b933624d59f6823db9ce7ec2e35b3e1e9c319c9db51fbeecfc3e319", upload-time = "2026-07-08T19:29:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:966d020354f465672dc7dd10d3a5c6cd17d7eb48620aa1d265b48a1f78f06898", upload-time = "2026-07-08T19:29:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0b8f7d0423027ae8b90c7977c627f3379f325363a08224dffad9b4b2d684a83d", upload-time = "2026-07-08T19:29:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3fbf9c9d1f3c10c2d59d04aca426dee9ccc6ceb32d255c61e93acc3b4f75fae6", upload-time = "2026-07-08T19:29:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:a17ff48608634db245e17e8bb00a9558554a49aeb1e4f5fe6cd039af2a10515b", upload-time = "2026-07-08T19:30:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:ac7aaf322be4777765a53bed7264a214dd81b3a1d276b93150515a3c5f75e4b0", upload-time = "2026-07-08T19:30:12Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:dec241fef3984c0d1edadd1f58708e218d4eae881ceef7bc10cf9964d41b68b9", upload-time = "2026-07-08T19:30:20Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ca021f9eb2f8345c83fa03e3a04587308afb8df71bd472670b3ece00df58621c", upload-time = "2026-07-08T19:30:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d20fa53ee744502fa4c69818a720b05ca0d37abd055d4f6e66cae155114bc691", upload-time = "2026-07-08T19:30:45Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:e2e5134decf00e218da62318f3dc5df156231d367871918e91eba95ab0ad43ab", upload-time = "2026-07-08T19:30:58Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:991cc14b39e751122c01f017be6448533989868731cb5eecd1006893d26787c2", upload-time = "2026-07-08T19:31:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7b8d26e29bceafbdaa8d63bfe7612f23875b5af2cc07e13f809c3ed890bbe1d8", upload-time = "2026-07-08T19:31:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b222c15a0fc2ce207d1c1a59700b46c8fa6748df1f447ad11e5c870dde0933d9", upload-time = "2026-07-08T19:31:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:a43376bd094124ef626bfdd3d4c2c62eacb0b5ddc99776f4a32d4fd16f1f3420", upload-time = "2026-07-08T19:31:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5002ca81af00ae69b57540f615b58b8ae922b6d4848176b366a52bd2196e6", upload-time = "2026-07-08T19:32:00Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:1a3a35229fdc13446b4eab50e7fcf9399ff941e89a3b761497786297a5d8dde5", upload-time = "2026-07-08T19:32:16Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:8e109528e6bab044815daebaf71770fbaace3a66ef1c816cb55c875350f78a60", upload-time = "2026-07-08T19:32:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp315-cp315t-manylinux_2_28_x86_64.whl", hash = "sha256:222a6681467cc7f6f05cd3068dfbc603def3a1e46d1d4620c1c8cdf6178bd563", upload-time = "2026-07-08T19:32:44Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, +] + +[[package]] +name = "torchvision" +version = "0.26.0+cu128" +source = { registry = "https://download.pytorch.org/whl/cu128" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2a04bf491eb22e6487defe6cead6ffaabbce13fb5981b8eb3540050f96cb0599", upload-time = "2026-04-09T23:21:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f44bfc61b9be80bcf52a762d34da363cea3125d10c01f37e271583803c7bb97b", upload-time = "2026-03-23T15:36:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:ed0770e00b96d8aa675718e20db69c740c927f027c9c8b1330251f8a973221b6", upload-time = "2026-04-09T23:21:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ed1324dbbbecb5a0149ed4ce8f9308465a1eef85ca2d2370dbb14805bf1c90aa", upload-time = "2026-04-09T23:21:34Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f2629d056570c929b0a1d5473d9cb0320b90bda1764bda353553a72cc6b2069", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-win_amd64.whl", hash = "sha256:d26091b15cd6e3c74c148d9b68c9a901ad6fb9b0f66fa3ea3ab09f04132a07d3", upload-time = "2026-04-09T23:21:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63e35234aed13b6edda37056f417b5c281249669db631e706811917af36b21d7", upload-time = "2026-04-09T23:21:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ccf26b4b659cfce6f2208cb8326071d51c70219a34856dfdf468d1e19af52c0d", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:8c0d1c4fbb2c9a4d5d41d0aaa87da20e525bcb2a154ce405725b0be59456804b", upload-time = "2026-04-09T23:21:36Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c4a9cacd521f2a4df0bcd9d8e96704771b928f478f1f3067e4085bb53a1da298", upload-time = "2026-04-09T23:21:37Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cb1f6184a7ba30fba40580e1a01a6604a86c55e79fdda187f40116ee680441ec", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:0232cb219927a52d6c98ff202f32d1cdf4802c2195a85fc1f1a0c1b0b4983a4d", upload-time = "2026-04-09T23:21:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e594732552a8c2fee2ace9c6475c6c6904fc44ccca622ee6765a89a045416a44", upload-time = "2026-04-09T23:21:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6168abc019803ac9e97efce27eafd2fdb33db04dcc54a86039537729e5047b29", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:367d42ea703844ecdb516e9d5eb09929012a58705d2622cf4e9e3c37f278cb85", upload-time = "2026-04-09T23:21:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b3865fa227661dd75b7b28c96d3d14e739bd08bf0614132758922fe0e7206f91", upload-time = "2026-04-09T23:21:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aac647c9130f1f25f5c8f5bca3d95cfd96bdfac93ab54529690b088e64e4fa64", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:6319e1ba49c6f62ac9902f73d0eab207b8a4dc6b4d3392fe9edd9903fff1be0a", upload-time = "2026-04-09T23:21:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e2ee9e16ee4518292694537fcbd20d2d27044e381d92b864f637e82795796a84", upload-time = "2026-04-09T23:21:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b5772c55bfda4377df8f1930d43c4e0231ef231b0228eade4b227c8d3ba6e34e", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:f160dc552a086244f7102c898f7be8ef46a41b36bce5ea80a4f2493cb30ca1fc", upload-time = "2026-04-09T23:21:41Z" }, +] + +[[package]] +name = "torchvision" +version = "0.28.0" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version == '3.14.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2a1ef4b6f4bf5828b48cfad97372c8982db906830884b2868ba5c3df937a7d81", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", upload-time = "2026-07-08T12:26:40Z" }, +] + +[[package]] +name = "torchvision" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/df/1ba039ad6cfe6e69209c36766b9b6e8c6fe92481c6d4e4ca52296f5f699d/torchvision-0.28.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2a1ef4b6f4bf5828b48cfad97372c8982db906830884b2868ba5c3df937a7d81", size = 1856019, upload-time = "2026-07-08T16:07:59.283Z" }, + { url = "https://files.pythonhosted.org/packages/88/ea/5c70ecf86f8e95174a85061cea78683a7bb7f422f09c3f3d4f30b7600fa9/torchvision-0.28.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:546fd85345cf8652f6cd099d4f9884b0ca5c2f3fae78689a21dd2f35ea6b622f", size = 7838211, upload-time = "2026-07-08T16:07:27.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/2f7ff1997d793e45d85fafa8374ee25348b7dae9ac521ba8751d7e1c75d5/torchvision-0.28.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6dfb0f45e2b4ceb4e76f158c3fbb5f44387099f3c466e3423a09ab665a194aba", size = 7669419, upload-time = "2026-07-08T16:07:41.648Z" }, + { url = "https://files.pythonhosted.org/packages/42/d0/2b3c30834ff23acd3854d0ff59bc580711f4b36d725de40105a852ed3719/torchvision-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:7fad44dc9582570c7d92c4487d36ac46998f40cc39b438e8b8f5111a935ce4e8", size = 3500355, upload-time = "2026-07-08T16:07:56.865Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, + { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822, upload-time = "2026-07-08T16:07:49.207Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718, upload-time = "2026-07-08T16:07:46.525Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/1494610ff54cbb154beb55033cc2cd50f3de04dac132fa2dd00e4f2b2556/torchvision-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:9a45ea67235d965ef52187130d20002a4de20c54ea3d927a24286961d268dc37", size = 3814319, upload-time = "2026-07-08T16:07:37.153Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/c1cab1ecbb3ff1a380a3f99283db1dee61b8afe354f6352c643b65937130/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", size = 1856020, upload-time = "2026-07-08T16:07:52.182Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/95233776e2def960e5abb7a07931230a545f43717a56a1e1140162033598/torchvision-0.28.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5cf78ebc401ce64ae19b8c55de866bb836797d559a4de9c25ccbe74cfa642d3a", size = 7842127, upload-time = "2026-07-08T16:07:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/e9b2495d0d57b9f60d63c57d0a910410a81b4b073bf70917bef815291119/torchvision-0.28.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:028a3d481b37d785605620d7cdad897064c5a55bae2aa1f2658766333e291940", size = 7675040, upload-time = "2026-07-08T16:07:58.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9c/55ed9cb6dfe3ee9c837df5cd0e758372e5829aa38b8dd71343aa632cc4e2/torchvision-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:87dc16b2df427c1318ad335f1e2be2b3b15b2cf20f7934c83b0505a48425ee5d", size = 4085785, upload-time = "2026-07-08T16:07:50.928Z" }, + { url = "https://files.pythonhosted.org/packages/20/55/08a726c14c67b37c8aca04b077766909f1c7ed23f76116884fe63b9bd033/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", size = 1856021, upload-time = "2026-07-08T16:07:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/40beacd53809194f5259e590d1afaeaa8ad57da15f77c646e6560bcc4616/torchvision-0.28.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb6dd6918460ed89cc7644adcc2402991474d6933cf1ce92b390641cb233fddf", size = 7797014, upload-time = "2026-07-08T16:07:43.04Z" }, + { url = "https://files.pythonhosted.org/packages/32/db/062cdb5a84380a60439775311fff34d89229760d2a50680393dc18699956/torchvision-0.28.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad7b3a439265cc3739a4ab5b4c998c0e38ea99c0ee7ca4dea35c5d0b099ec237", size = 7674669, upload-time = "2026-07-08T16:07:38.91Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a6/b4081e2d04e1541abf82785ac9e5178a494c19330391f551356c8c18b7b3/torchvision-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e9dd6f60d6e15f8dc27d4f877fdb6002fc70d70272412135f1c2ff9cfa08d3b", size = 4157380, upload-time = "2026-07-08T16:07:40.22Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b9/da40eca5bbe9596c12ae9899ab7abaf887f5e20f29d08b924b4633714821/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", size = 1856014, upload-time = "2026-07-08T16:07:44.282Z" }, + { url = "https://files.pythonhosted.org/packages/06/d6/313aafd3df4eaf5f330211bd4e75b7598bddbfee4f55580d3b58536e1b20/torchvision-0.28.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:89f90e29b0966352811b12589f3a3c61943bf2bb9487b9d7bbec10efb1096bb5", size = 7796873, upload-time = "2026-07-08T16:07:30.907Z" }, + { url = "https://files.pythonhosted.org/packages/b3/41/31f8e959ab8f942600b6357f8999c21d779d5fd3304b0fd204ff4b518239/torchvision-0.28.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:36beb0782976906069ca03d4c9aacaf4b6b838b06ed6c20960ea9c51cce7acdd", size = 7674634, upload-time = "2026-07-08T16:07:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/15/15/4c5115253fd470672cdac0a1cf139e06b4f3e29d041238a2b255937f63be/torchvision-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:3557cc7b539f46dabcda2b6f2b14017ccbeef024de466d4fc5835fc3f287f769", size = 4184005, upload-time = "2026-07-08T16:07:35.805Z" }, + { url = "https://files.pythonhosted.org/packages/6a/80/822a6163da716f8a78141cf6678d74e26a572285d4ea866ef8aa657bb307/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", size = 1856011, upload-time = "2026-07-08T16:07:33.404Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d1/cd3f9463b39a790ec8c0c2f6e6c8061edb1562114d04fcdfa786ed889345/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:62c7d110f86a039245b587e4fae60278c649f3bd42ff79cfbc1178eca4e72542", size = 7796742, upload-time = "2026-07-08T16:07:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/3e0a7ad18e99831e2d7f4713d3be717b7159ff5a920862dd5c23c454aa71/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:904cf89af220f8c6b2ed0296bb5065b474ce43b77558e48b2bf9de8b0ba17204", size = 7675526, upload-time = "2026-07-08T16:07:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/23aea03b28297bc66a4461f55ae4296368a9d85fa9a454bafcb2a5348bd7/torchvision-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:46f581979c010ad6da6bd85ee602aa707e1ff44312670223b7a0ee517ad06d47", size = 4291452, upload-time = "2026-07-08T16:07:32.236Z" }, +] + +[[package]] +name = "torchvision" +version = "0.28.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version >= '3.15' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7d81da2804da52c9788f2d5a8d0aaddcea9fce6eb5d7c6e19a32b40b4ed0b75a", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:fc38699d69d11e563a5b1c02f621f639c4caa9e2ffe6b1ddd4aceedffc0d0578", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:cf5d1c4c355c5b487e7d1c681ef28b9caa2ff0dfa3a32fdcd9e98206a1462bf4", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22958193d72444ed7cbcc665ba4821a31e5279f9c4d1ad08520918b30896b78a", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1dad604dfc0177ecebe0891bd9701fe2c62ec3f7819a247be541b3fb6effee99", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:7b6667fd0172463be2a271fb0dbd44b31a7891afd549a66208613ce4cdd79f88", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2f768c4f6d5adf6d5535061fd69ec44827608bac0e96e12114942a6fdfce1107", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b545d46f4d2f9d30381281cf22874bfe1d32a8a7b0ee8396fccde89f30c6a9d9", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:d88db83abbdfb97199979ec94dd427bc372c1b9ab01f0dbed20af05b0bd644b1", upload-time = "2026-07-08T12:26:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:879ae6d4e2e3651582fb7187eafd535601cb5d019595d47e2c874262a000e88e", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6373ec4c2f922e89f45ac91889404d312ba29a31f205b0ad9a725a3894ca246", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:d63eae114b4d1fca2b294d300cea3f0d6c71b6d132641e0c4cab1aa06a467b0d", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3a1a76c8decb1d7bbedd3588bccc90fb269944b7321a773db181735b42115422", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:1aa741ae0eb8668b6287dd667548e2dd10179c828db68bfdee1519763b9c5b99", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:d2a0171faa211b506c4dcf3a036942a41077c5d2d3d94883dafbda7b8624a3eb", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8d8b98608779c770ede5e20609772453ebc7487ebb8697445d1856466c542f45", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:870d0d42f2eb80f4870cd35e51eea52f596a408a671b28136f06a808846f24c5", upload-time = "2026-07-08T12:26:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.28.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:82dbffb63d61cd43d9c7a311588e665aa2b21173a05f852b4d384d6782fd88ef", upload-time = "2026-07-08T12:26:39Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, +] + +[[package]] +name = "transformers" +version = "4.57.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" }, +] + +[[package]] +name = "triton" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/ba/b1b04f4b291a3205d95ebd24465de0e5bf010a2df27a4e58a9b5f039d8f2/triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781", size = 175972180, upload-time = "2026-01-20T16:15:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.14.*'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + +[[package]] +name = "ttach" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/5d/4c49e0eca4206bc25eff4ba89cee51b781466e2e3aad2f1057fd5d2634be/ttach-0.0.3.tar.gz", hash = "sha256:120c4dd881feb0e9c8dd63b154f2655891c3e20689b68a94d162bfd5557bcb48", size = 9600, upload-time = "2020-07-09T14:44:09.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a3/ee48a184a185c1897c582c72240c2c8a0d0aeb5f8051a71d4e4cd930c52d/ttach-0.0.3-py3-none-any.whl", hash = "sha256:7000bb4334f856b0c79a341df386c92f1c76faf091043cc3cd7f541d2149faf8", size = 9839, upload-time = "2020-07-09T14:44:08.006Z" }, +] + +[[package]] +name = "typer" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, +] + +[[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 = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.51.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "(platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32') or (platform_python_implementation == 'PyPy' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'cygwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/5a/2bf22ecb24916983bf1cc0095e7dea2741d14d6553b0d6a2ac8bc96eca93/watchfiles-1.2.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9", size = 400471, upload-time = "2026-05-18T04:31:08.908Z" }, + { url = "https://files.pythonhosted.org/packages/55/70/dea1f6a0e76607841a60fb51af150e70124864673f61704abb62b90cdcc7/watchfiles-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4", size = 394599, upload-time = "2026-05-18T04:30:19.845Z" }, + { url = "https://files.pythonhosted.org/packages/18/52/752dcc7dc817baef5e89518732925795ce52e36a683a9a3c9fb68b21504e/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631", size = 455458, upload-time = "2026-05-18T04:30:29.126Z" }, + { url = "https://files.pythonhosted.org/packages/12/48/366ebbb22fcc504c2f72b45f0b7e72f40a18795cc01752c16066d597b67a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994", size = 460513, upload-time = "2026-05-18T04:31:40.85Z" }, + { url = "https://files.pythonhosted.org/packages/ad/44/1f9e1b15e7a729062e0d0c3d0d7225ea4ab98b2267ef87287153be2495fc/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e", size = 493616, upload-time = "2026-05-18T04:30:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/7e/55/8b1086dcc8a1d6a697a62767bd7ea368e74c61c6fd171683cfe24a3fe5d2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19", size = 573154, upload-time = "2026-05-18T04:30:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/14/7a/242f400cc77fafa7b18d53d19d9cb64fc6a6f61f28c55913bae7c674d92a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8", size = 467046, upload-time = "2026-05-18T04:30:41.869Z" }, + { url = "https://files.pythonhosted.org/packages/02/c8/79eee650c62d2c186598489814468e389b5def0ebe755399ff645b35b1b2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07", size = 457100, upload-time = "2026-05-18T04:31:13.064Z" }, + { url = "https://files.pythonhosted.org/packages/81/36/519f6dbb7a95e4fe7c1513ed25b1520295ef9905a27f1f2226a73892bfb7/watchfiles-1.2.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551", size = 467038, upload-time = "2026-05-18T04:30:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/2f/12/951af6b9f89097e02511122258402cb3578443021930b70cf968d6310dc0/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310", size = 632563, upload-time = "2026-05-18T04:30:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/0cba1f0a6117b7ec117271bdc3cb3a5a252005959755a2c09a745e0942cc/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df", size = 660851, upload-time = "2026-05-18T04:31:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/26347558cc8bf6877845e66b315f644d03c173906aa09e233a3f4fd23928/watchfiles-1.2.0-cp310-cp310-win32.whl", hash = "sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1", size = 277023, upload-time = "2026-05-18T04:30:18.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/68/a5e67b6b68e94f4c1511d61c46c55eba0737583620b6febf194c7b9cc23f/watchfiles-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d", size = 290107, upload-time = "2026-05-18T04:32:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, + { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, + { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, +] + +[[package]] +name = "websockets" +version = "16.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204, upload-time = "2026-07-17T22:51:05.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/e7/d1671fb984f9dd844e1da5288070c7c23c9eaba3082d3871aae19c3ab8b9/websockets-16.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:49ae99bdfcae803a885c926bf14f886196e84925395bb3f568fef5c0f0979d7d", size = 179570, upload-time = "2026-07-17T22:48:24.032Z" }, + { url = "https://files.pythonhosted.org/packages/99/f5/70df723bf571f5e0b1b845e0a4ff1c966eeb84f667599fc251caa37d15a3/websockets-16.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5bfd1ac19b1b9986a9c95a82d5e23a391ebb09e12c34d7be6094b86efcc35731", size = 177252, upload-time = "2026-07-17T22:48:25.775Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/2f14b2e167170b8bf1c8bb7f9b0d78000f470d41a2085a91f33e3917b6c9/websockets-16.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9246a0d063cfcbcc85f2359dd6876d681213f4790832272aa16641b4ed5d64d4", size = 177530, upload-time = "2026-07-17T22:48:27.337Z" }, + { url = "https://files.pythonhosted.org/packages/f3/18/a17e2f0cde02dc10154c808deed7e1d8528afff93612f70d3f0a5b19b011/websockets-16.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1214e673c404684b9bf7154f5cf43b45025b1a6160fac3a9e438e9c1a97e22cb", size = 186038, upload-time = "2026-07-17T22:48:28.756Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b0/41de283899cf5929d637b72a508cdbc9aa40dc0f317c6b77613fd1000488/websockets-16.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90001d893bc368e302ef168d82130b4e4fdd27b85fa094682df9b667c2d48838", size = 187278, upload-time = "2026-07-17T22:48:30.328Z" }, + { url = "https://files.pythonhosted.org/packages/50/61/874aab5257e027f9f61b5004cec65e592babca7942b1bc09f38e72b7f1fd/websockets-16.1.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:130937b167a52af203c8d58e78d67705874e82759862e3b9671a452fec4abc87", size = 189936, upload-time = "2026-07-17T22:48:31.896Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1a/42173913ac5519607220849ed417c864d77384e4119f06dbba964a50f096/websockets-16.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c9f23004a3d40e89c01a7955d186a6cc83418d93b749701944ce2de3e95a1f3", size = 187796, upload-time = "2026-07-17T22:48:33.344Z" }, + { url = "https://files.pythonhosted.org/packages/1b/f4/37c1840bd89b529479aec41470b97b7c683b107ca90b6399ac5afb99dedf/websockets-16.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f55f0b01956a094c8587146d9558c91937e78789c333860ffaf35931a6e5dbc4", size = 186481, upload-time = "2026-07-17T22:48:34.843Z" }, + { url = "https://files.pythonhosted.org/packages/9e/70/652d9b964adcfbeb056f42e0ca6bece34d108fe75534e74df20643cae199/websockets-16.1.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6aaface73b9c71974c6497366d8b9628357f6c9749e09c4ea3610176c63f2ae3", size = 184351, upload-time = "2026-07-17T22:48:36.307Z" }, + { url = "https://files.pythonhosted.org/packages/13/f1/af3850e5d48d482921985be72ebcb169c6180b3a77b57bd612deebcee23b/websockets-16.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc0fad4933f427acd5b1cec210f3ea6dce7089e1724e4b9ec6ef47c6c04d1b3b", size = 186791, upload-time = "2026-07-17T22:48:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/1d/40/1a4e3ed4969ec378dcad337e5f1472c5e292cb3e733bc392f0dc2e230abd/websockets-16.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2769a0344a09e9ccf5b3cce538bc75a51b53eff3275d3896310c8552049195d", size = 185413, upload-time = "2026-07-17T22:48:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3e/4e3fa1afe8f1a6a780434cd9ba8eb422632b044eff3dd73f6af67523c147/websockets-16.1.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f70541f3104339f59f830522d94ebadb1bf47426287381623443d8bb1cdbf33d", size = 187178, upload-time = "2026-07-17T22:48:40.676Z" }, + { url = "https://files.pythonhosted.org/packages/71/ab/dd742766aa5dda7f349be0de49e4d565b84cf6f7f7fa02e07692f0f2bdd9/websockets-16.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:dc385593a42e31cd6fb60c19f0ecb015b386603818fc2c6c274fb42bd2bb4165", size = 185051, upload-time = "2026-07-17T22:48:42.098Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f5/76438c6560f416f1c0a7f587679fb97cc6e99ed336011d43ce2002dd27c1/websockets-16.1.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:387e8e4aa5df2f90b198fa3cad3478822a89cf905b6a6d6c97dc3664689640cc", size = 185846, upload-time = "2026-07-17T22:48:43.472Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/5c0320f2127823d27b2d56d611d31b0b284ad4edcb41364d66bf4c92b537/websockets-16.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fd46fff7eb62c24804d234f0051c7a8ea81285ad63e0337d3dcf33ca82aee58a", size = 186066, upload-time = "2026-07-17T22:48:44.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/97/875986b857b955c3f9dd192cb8a1af81254dfb2ea22cc9590f0a1e020b8b/websockets-16.1.1-cp310-cp310-win32.whl", hash = "sha256:7883388947767080f094950b342b30d35a2a06b849cd967c422fa0db72b40ea9", size = 179940, upload-time = "2026-07-17T22:48:46.481Z" }, + { url = "https://files.pythonhosted.org/packages/54/82/1013a5fe7ddae8e102bc3b4b39db81d8d28fd02100a324ce6ede8cd832b1/websockets-16.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d57685547e0060cc6fd90ee6a28405d6bd395e525545f13c8d7cd99c78afd79f", size = 180239, upload-time = "2026-07-17T22:48:48.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/03/47debfe28e9d6d354be5d777b67fd44c359b9eb299a5d103500bd7cc3e37/websockets-16.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d0fcf657e9f13ff4b177960ab2200237b12994232dfb6df16f1cfe1d4339f93c", size = 179566, upload-time = "2026-07-17T22:48:49.596Z" }, + { url = "https://files.pythonhosted.org/packages/72/93/31efa1ed78c17e5cfc229fd449e3966e1b9cc15753204cd585cc8dd01f4a/websockets-16.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b852788aa51764e2d8e4cf5493d559326bcae5e38d16ba25ffa322b034df272a", size = 177250, upload-time = "2026-07-17T22:48:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/542378ab3972b0c1cf1df3df3eff9591cea0d30c58c3aa3c4ddbc244e787/websockets-16.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1427fb4cf0d72f66333e2cacc3ff5f575bf2d7008166ce991a4a470b21d51a22", size = 177528, upload-time = "2026-07-17T22:48:52.59Z" }, + { url = "https://files.pythonhosted.org/packages/33/d9/162321f63c7eed558e9e1798ed7a1e34a4f6dab51f35419e4ed7a4907979/websockets-16.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:da4ca1a9d72f9030b3146b8d7022719a9f3d478f61efe6f7dd51d243f61c51b2", size = 186859, upload-time = "2026-07-17T22:48:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/de/09/87df740f7430ce564bd52402e9c9458d4d0459cc7d2ee29e530c8204851b/websockets-16.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86d7f0f8bdb25d2c632b72527325e4776430fd5bc61b9118de4e2b8ddb5f5b01", size = 188095, upload-time = "2026-07-17T22:48:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/d2/12/3d2703af7cc095f3c81904c92208cc1ae79affbc67376944b50ee9301f73/websockets-16.1.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dfcad78ea1492ee3a9ec765cb7f51bbc17d477107aaf6b22abf7b2558d1c5a0", size = 191385, upload-time = "2026-07-17T22:48:56.742Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/986aa0234a964a00f5149cfc46e136e96c8faad1c783474550f40d31aef4/websockets-16.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb9a0a6dc3d1b3986cb88091b6899f0396651e0f74e2c9766ab8d6ffc3842e29", size = 188653, upload-time = "2026-07-17T22:48:58.134Z" }, + { url = "https://files.pythonhosted.org/packages/35/6b/10f9d03e3970a69ba67bd3b46b87a929b586d0300fadbfe14f57c1f85490/websockets-16.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29dfa8114c4a620c69591c5973860f768eac29d3fd6904f37f34266cb219c512", size = 187426, upload-time = "2026-07-17T22:48:59.515Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/bb3aad62bf63d8bb3f0634b2eabffcfb3677a34bd19492110ff6869cf703/websockets-16.1.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ff9417c0ada4d0f7d212f928303e5579bdf3ace4c802fa4afabb30995da58c3", size = 184882, upload-time = "2026-07-17T22:49:00.916Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4c/c09a2ea9bfbeccce52fdc383e5f28af4bc8843338aabac28c81489af6120/websockets-16.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fe0b50da2d84535fb4f7b4bfa951280f97ce3d558a0443b541166d609e67b57", size = 187584, upload-time = "2026-07-17T22:49:02.283Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8b/31bb4eb4d9eaacf1fdd39d115772a8aeaedfc19b5dc262e57ffbc8a9d42c/websockets-16.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34420aaa64440ebd51ac72ca8a45ef4626429438c9b02e633ae412ed43f925d3", size = 186174, upload-time = "2026-07-17T22:49:03.973Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e4/dc02d725610a1ad49e193ef91a548194d71bdc6cdf27da83067dd1f73995/websockets-16.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a6a61aff018180c9c50b7b0da33bfd29d378af3497429c95006c589a23a11648", size = 187986, upload-time = "2026-07-17T22:49:05.553Z" }, + { url = "https://files.pythonhosted.org/packages/e0/73/30ed84c8bfd14c73d4af29d5ed9323c3073b48e0b7b23b67070f4e7fd59b/websockets-16.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:04fd29a0e2fe9414a95b00e92c67ae51bf900c50c0f8a4b2dafdad621f49ea1d", size = 185565, upload-time = "2026-07-17T22:49:06.959Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d3/4be8d4959f51e31b4f8fc0ece12b45bd3b6c0d15ea23b9990d9c11fc805f/websockets-16.1.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5c31aa7e39ee3e8a358573257f1c0bb5c52430d1b637030dd9c8cc2c282926be", size = 186598, upload-time = "2026-07-17T22:49:08.293Z" }, + { url = "https://files.pythonhosted.org/packages/26/fa/abb38597a52d84ed9cfacadc7a0c6f2db282c0ab23cdf72b58a666a21227/websockets-16.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d14bfb217eb4701e850f1525c9d29d79c44794cdf1c299ead25f39f8c78dea81", size = 186834, upload-time = "2026-07-17T22:49:09.766Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/1119ad08a228b90c4eb77fbe48df7836731a605f5f881ba701ca826a4a65/websockets-16.1.1-cp311-cp311-win32.whl", hash = "sha256:2e28e602bb13da44fbe518c1781a88e3b9d4c3d48d02c9bad83e546164336f57", size = 179940, upload-time = "2026-07-17T22:49:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/71/b2/e511c1c6f64a95c2f3fc54bffda0e14eaa7e9442be605c29270f7589b918/websockets-16.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:7421fad442de870a8cbf2287d1cad7e706ece0dbfeba5e911df132cbdc1cb56a", size = 180239, upload-time = "2026-07-17T22:49:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/17/9d/681cda21c9eee743203a6cb79b9d3d05adad9aa60ec660c6c9bf4dd619ca/websockets-16.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cc97814dfb786a83b6e2dc2e79351e1b83e6d715647d6887fcabd83026417a00", size = 179600, upload-time = "2026-07-17T22:49:13.92Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8d/6195a88b45e8d2a8f745fc2046e36f885a3c9763e6767d2c46229bf9510c/websockets-16.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e047dc87ef7ca50f4d309bf775ad4a71711c58556d75d7bd0604b2317f43e94b", size = 177272, upload-time = "2026-07-17T22:49:15.453Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/fe2d498c64dea0095c9a9f9a351af4cd6eef31b618395582bc1f38ba45ff/websockets-16.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01fbdcbac298efe19360b94bc0039c8f746f0220ba570f327577bfee81059175", size = 177542, upload-time = "2026-07-17T22:49:16.875Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ed/f1831681fce0e3242346e5458486003c5f124ed69e5e0b847fd029db4973/websockets-16.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f62863e8a00a6d33c3d6566ec0b89f23787b747ffe0c3bc71ec0e76b82c94b1", size = 187137, upload-time = "2026-07-17T22:49:18.323Z" }, + { url = "https://files.pythonhosted.org/packages/6f/79/4ff9dcc1bb46f6b4c536936dde1fd60f9b564f3304307274db97f4c9496d/websockets-16.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8087e82f842609734c9b5a1330464f8e94e346ba0e18c832c08bafa4b0d63c15", size = 188374, upload-time = "2026-07-17T22:49:19.65Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/5c49b6efb36cab733d23773f6de575e1dba65736ead17d5d2b2a1daef779/websockets-16.1.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2bb5d041a8307d2e18782e7ce777f6fdb1e8c2f5d09291484b18c294b789d9aa", size = 191155, upload-time = "2026-07-17T22:49:21.331Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f6/56ccceda3a4838d18f1d40821480da4775397e8b1eecf4031e20c50e2e90/websockets-16.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1db4de4a0e95673f7545d393c49eeb0c2f18ac1ef93073218c79d5cdb2ee75ab", size = 189011, upload-time = "2026-07-17T22:49:22.889Z" }, + { url = "https://files.pythonhosted.org/packages/86/d6/ad5286241a2bce1107e2798d3bfbd62cf79aee167bdb654f8cb1e9dbf949/websockets-16.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f17dbe07eb3ea7f99e4df9b7e0efefe80fbf30d37a8cc4d561a0aed310bc8847", size = 187766, upload-time = "2026-07-17T22:49:24.339Z" }, + { url = "https://files.pythonhosted.org/packages/bc/67/d65c970b7e347fdca69479beb7811c2060529956730a7a4e3ae7c66b0e31/websockets-16.1.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b57693728576d84ede0a77987ab16881b783d2cd9f1dc180a8fbbc3f79c4428", size = 185173, upload-time = "2026-07-17T22:49:25.743Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5b/14af3cd4ee69d8ea9baca58f3dc3cfb1ba78332a347fd478cb096549d60e/websockets-16.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a636ff1e7a5c4edf71ef0e79adae7f25dba93b4fcbe3dc958733477ffeb0eaf", size = 187809, upload-time = "2026-07-17T22:49:27.147Z" }, + { url = "https://files.pythonhosted.org/packages/7b/11/be301710d70de97e3e7b3586e6d492c9c06d6a61bf1c2202c36cf0c75607/websockets-16.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d6bec75c290fe484a8ba4cacdf838501e17c06ecfbbf31eede81a9e431bd7751", size = 186412, upload-time = "2026-07-17T22:49:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/db/07/fe1435bf6fe738a3d3b54dbe0c18dabf12cba4d909ac8b58b539ce27c1f4/websockets-16.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:54509b8e92fee4453e152b7558ddef37ce9705a044922f2095a6105e3f80c96f", size = 188290, upload-time = "2026-07-17T22:49:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/81f394aff8efcbb01208c1ced77df0a3c7fcce584a88c7273663697946c2/websockets-16.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f0aa4aad3b1b69ad3fd85a0fd0952ec64331c762bd77ec51cc814170873890b2", size = 185844, upload-time = "2026-07-17T22:49:31.447Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/dd485b995473f415510251fe9bd708f2d24458f439fce958daf8d66dc7c6/websockets-16.1.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:42290eb6db4ccaca7012656738214f8514082fb6fa40cdeb61bb9a471b52e383", size = 186823, upload-time = "2026-07-17T22:49:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0b/f78de76ff446f1e66af12b43c48a35f31744de93cfdec2f4ea67d5d7bbf1/websockets-16.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:53260c8930da5771cec89439bff99c20c8cb03ddb9588b980697355a83cd4bd3", size = 187102, upload-time = "2026-07-17T22:49:34.616Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/4cf892007778eaf84ad162bfc98046e0ed89b63ac55949e3236626b2a23f/websockets-16.1.1-cp312-cp312-win32.whl", hash = "sha256:1d27fa8462ad6a1cb36206a3d0640b2333340def181fae11ed7f9adeaa5c0747", size = 179943, upload-time = "2026-07-17T22:49:36.213Z" }, + { url = "https://files.pythonhosted.org/packages/d9/de/6abe251d28c3a3f217096575400b27750b18e0b1d2fff3a2a239960fea07/websockets-16.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:b436f6ec4fc3a6b4237c84d3f83170ed2b40bb584222f0ac47a0c8a5921980c7", size = 180243, upload-time = "2026-07-17T22:49:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fd/6ec6c6d2850aea25b1b2aa9901a016980bb87d01e89b3eb00470b1b5d471/websockets-16.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1", size = 179587, upload-time = "2026-07-17T22:49:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d8/1d299d2dd34087db39831a34cc645ef8a6f89d78efada6983093513cd81c/websockets-16.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df", size = 177272, upload-time = "2026-07-17T22:49:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3d/86/0a70d3ae2f0f2256bb41302d9804dbca65d4360281e7feb3e1f94102ac46/websockets-16.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac", size = 177530, upload-time = "2026-07-17T22:49:41.786Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c2/c676c69444d9db448b3f0a55a98dcc534affce0bce961d9d2f0b8499b10a/websockets-16.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8", size = 187197, upload-time = "2026-07-17T22:49:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0b/13/88137fbaf726ebe29d62c1117fa11fa2bbb6209dc79d4ad738efbe36a2aa/websockets-16.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6", size = 188433, upload-time = "2026-07-17T22:49:45.147Z" }, + { url = "https://files.pythonhosted.org/packages/01/6d/46c2f2ce6751cb26f39293e1ecbf8544cb01321397cd476c2756b98c216d/websockets-16.1.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854", size = 189868, upload-time = "2026-07-17T22:49:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/29/2b/170a9e8097636cfde4dc3c592b6e00b18a44a2f5407606d96ca542dd5838/websockets-16.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a", size = 189059, upload-time = "2026-07-17T22:49:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/a7/48/f0d4ebc9ab4b473b8861b9e20fdb663d515d42f7befdf62cdb60fee7a1ec/websockets-16.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49", size = 187814, upload-time = "2026-07-17T22:49:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ba/39a41d3ae8e72696a9492581900611c5a91e2b07563b0bcd2523adea9854/websockets-16.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785", size = 185229, upload-time = "2026-07-17T22:49:50.787Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/ac15b604f850d1907f0a85ed721cefe47cd45034b3620069b829746cccbe/websockets-16.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56", size = 187874, upload-time = "2026-07-17T22:49:52.228Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/3fbd5d71d59299c3770faa5884d4f45070236ca5a35ab3a61830812c409a/websockets-16.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509", size = 186469, upload-time = "2026-07-17T22:49:53.776Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/dd90349bba58af2a53ef2ddd9c32716c81eb6d59a0687939fff561860878/websockets-16.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1", size = 188347, upload-time = "2026-07-17T22:49:55.202Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/f73ba86427682da59b78c11d77ba56d5b801c32e84afe79b274bbd6a9bb2/websockets-16.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead", size = 185903, upload-time = "2026-07-17T22:49:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/7c/f95eb20e80104173b3a0a092291f89ea4047ef6e608e0a57ca06eb14eecb/websockets-16.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e", size = 186855, upload-time = "2026-07-17T22:49:58.467Z" }, + { url = "https://files.pythonhosted.org/packages/b0/35/dd875b3e050ff232d60fa377707f890e369f74d134f1be32e8f68879747c/websockets-16.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87", size = 187140, upload-time = "2026-07-17T22:50:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/e8/dc/5cbfcb41824502f6af93b8f3943a4d06c67c23c7d2e31eb18748c4a5b2a7/websockets-16.1.1-cp313-cp313-win32.whl", hash = "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea", size = 179928, upload-time = "2026-07-17T22:50:01.685Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c1/71e5deb5b7f8f226997ab64908c184ac3105c0155ce2d486f318e5dd08a8/websockets-16.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68", size = 180242, upload-time = "2026-07-17T22:50:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/73/a2/ba78a164eeea4620df4a4df4bd2ed6017438c4655cc0f36f2c0bc0432355/websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8", size = 179635, upload-time = "2026-07-17T22:50:05.001Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/d26d7a7628cd4ac34cbbdb63ac80914ca842ed8e42938c40a53567806df3/websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293", size = 177320, upload-time = "2026-07-17T22:50:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/0f/45/ebec83e6269536aa5932533c67b0af5c781f3e73fdbcd68672dcf43f4f44/websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051", size = 177544, upload-time = "2026-07-17T22:50:07.834Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d5/abc614d2297f6c1c3e01e61260364457a47c25cc1cf6a879038902bc6aa8/websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1", size = 187270, upload-time = "2026-07-17T22:50:09.275Z" }, + { url = "https://files.pythonhosted.org/packages/52/71/4c99af3b87dff1b2927981f6876607d4acb45338c665242168d3982f7758/websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7", size = 188509, upload-time = "2026-07-17T22:50:10.722Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5c8ca14b0df7eb84ed0524165c5359150210140817a3312aee57bf62a1cf/websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31", size = 189882, upload-time = "2026-07-17T22:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/25/c1/bedfba9e70557129cb8083748d167bdcc01483dedf0f0df143676df05cbe/websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0", size = 189114, upload-time = "2026-07-17T22:50:13.789Z" }, + { url = "https://files.pythonhosted.org/packages/df/09/aa835b2787835aebd839114be5de51b797cb480b63ba42b26d34dfe147cb/websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3", size = 187861, upload-time = "2026-07-17T22:50:15.179Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/f6408330694dbc9830857d9d23bc14ac4f6875127a480cfdda8d5ca21198/websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562", size = 185286, upload-time = "2026-07-17T22:50:16.741Z" }, + { url = "https://files.pythonhosted.org/packages/17/9a/e0675e70dd8a80762cf35bb18799d3f290a4890ffe6439bc51d222796083/websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b", size = 187935, upload-time = "2026-07-17T22:50:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/33/c1/3234cfb86afde01b81e9bddcc6e534c440975d60a13991259e833069ab3e/websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a", size = 186444, upload-time = "2026-07-17T22:50:19.67Z" }, + { url = "https://files.pythonhosted.org/packages/89/87/9c15206e1d778923d8daa9657de07aa62ea815e13448319c98458c37b281/websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c", size = 188409, upload-time = "2026-07-17T22:50:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/cf5de5c67676de2d3eef8b2a518f168f6796595447a5b7161ba0d012915c/websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499", size = 185958, upload-time = "2026-07-17T22:50:22.719Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/731b6ddede2e4136912ec4cff2cffbda35af73546be4762c3d7bd3bd79af/websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985", size = 186911, upload-time = "2026-07-17T22:50:24.108Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7f/39c634472c4469a24a7c09cecddffb08fac6d0e74f73881a94ee8a40a196/websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9", size = 187204, upload-time = "2026-07-17T22:50:25.548Z" }, + { url = "https://files.pythonhosted.org/packages/26/89/9667c256c256dafcc62d21328ce7a40067da857969b68ee9af375b0aaf72/websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328", size = 179603, upload-time = "2026-07-17T22:50:27.086Z" }, + { url = "https://files.pythonhosted.org/packages/bd/dd/1c099d6c0fc5deb6b46ccdbb6981fdb4b12c917869cb3952408409dc18db/websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc", size = 179948, upload-time = "2026-07-17T22:50:28.521Z" }, + { url = "https://files.pythonhosted.org/packages/35/25/9956b2d5e0529d5d23924f21bba1440d4c5c88a562e4f08550871ffa97a7/websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573", size = 179963, upload-time = "2026-07-17T22:50:29.982Z" }, + { url = "https://files.pythonhosted.org/packages/17/06/55ffc976c488b6aee9ea05761ff7c4e88e7c1fd82818c8ca7b556ad2f90c/websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999", size = 177497, upload-time = "2026-07-17T22:50:31.396Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/f7dac2e980bacc92bdc26cebae4ae4d50cae5380732c50980598fc0bbae4/websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe", size = 177698, upload-time = "2026-07-17T22:50:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/b2/39/26762f734113e22da2b942c3aca85798e0c0405d64c256549540ff31e5a1/websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d", size = 187561, upload-time = "2026-07-17T22:50:34.24Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/c3f330851806b9b02138b774d593478323e73c99238681b4b93efe64e02d/websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392", size = 188732, upload-time = "2026-07-17T22:50:36.088Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f2/eb2c450f052de334ae33cf200ece6e87b0e14d186807074e4eb1cd2cdea2/websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7", size = 190872, upload-time = "2026-07-17T22:50:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/70/31/2ac8cecf3a74f7fed9132129fc3d90b3998a1554570c11a69b2a8c20332d/websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499", size = 189305, upload-time = "2026-07-17T22:50:39.53Z" }, + { url = "https://files.pythonhosted.org/packages/6a/cf/8ab19650d3c0d4562c92e70ab47c257c4aa5c6a713ed87fe63766b31fefc/websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43", size = 188033, upload-time = "2026-07-17T22:50:40.912Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/a49a38a6127a4acb134fb1912b215d900cc657605cff32445bf519f3acc4/websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458", size = 185748, upload-time = "2026-07-17T22:50:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/ad1fa40388c7f2e0bb2c7930d0090b6c5498594bd1cdaec18864df3d9e97/websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62", size = 188285, upload-time = "2026-07-17T22:50:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/d5db28ca264b9104f82196f92dc8843e35fd391f763d42e4ad358f5bc97e/websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb", size = 186777, upload-time = "2026-07-17T22:50:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/726cb39d0cc43ae848dce4aa2acb04eecc6738b1264ec6d700bf6bcfb9f8/websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51", size = 188682, upload-time = "2026-07-17T22:50:46.973Z" }, + { url = "https://files.pythonhosted.org/packages/be/c7/1168704de8c2dd483edabe4a22cbe4465dd8be8dd95561d214f9fe092871/websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0", size = 186377, upload-time = "2026-07-17T22:50:48.413Z" }, + { url = "https://files.pythonhosted.org/packages/ca/40/f9ff2d630ffce4e7dfea0b2288e1caf9ebbf9ff8a9ec9396136ce8b94935/websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217", size = 187148, upload-time = "2026-07-17T22:50:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/e177c8299f78d7cbe2d14df228643c10c70c0e86e108e092056bbcc16e46/websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737", size = 187578, upload-time = "2026-07-17T22:50:51.619Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/b6987faf330f5af5c787a2610124c2e8403d51724f9001ec4fff6311fe7a/websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7", size = 179729, upload-time = "2026-07-17T22:50:53.269Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6e/fbac6ed878dd362fbad7d415fa4f84d38e3e33fed8cde45c64e783acf826/websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231", size = 180072, upload-time = "2026-07-17T22:50:54.969Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ed/71fea6e141590cafc40b14dc5943b0845606bee87bdb52a21b6a73eb4311/websockets-16.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:820fb8450edddae3812fd58cbc08e2bf22812cb248ecb5f06dbb82119a56e869", size = 177185, upload-time = "2026-07-17T22:50:56.665Z" }, + { url = "https://files.pythonhosted.org/packages/01/ec/00e7eeca200facf9266a83e4cbbf1bed0e67fba1d4d45031d3e5b3d81b5c/websockets-16.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:125f22dbefaf1554fea66fc83851490edb284ce4f501d37ffed2752f418332d9", size = 177459, upload-time = "2026-07-17T22:50:58.197Z" }, + { url = "https://files.pythonhosted.org/packages/75/fd/5774c4b33f7c0d8f0c51809c8b3a93456c48e3543579262cfa64eb5f522e/websockets-16.1.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30bbe120437b5648a77d3519b7024ea09530e0b5b18d3698c5a0ae536fe0cc2e", size = 178294, upload-time = "2026-07-17T22:50:59.641Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/48e2c03d2bd79bb45948841c592d24156312dd5f58cdf8f549febe652fb6/websockets-16.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6b9dadbef0cccd9f4c4ee96b08898afa73e26803bbe0f6aeb5bb12b0074206d", size = 179190, upload-time = "2026-07-17T22:51:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/73e511ecf2496ceac57dd4ed8388efe2bcf0769338a2dbf242c8366ae87e/websockets-16.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56cd5fc4f10a9ea8aa0804bddb7b42506cf9e136046f3b4c27de8fec9e2ecba5", size = 180330, upload-time = "2026-07-17T22:51:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/be/4d/2d0d67834092e354d2b0498f014a41249a89556bc406cf86f3e1557bb463/websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3", size = 173814, upload-time = "2026-07-17T22:51:04.184Z" }, +] + +[[package]] +name = "wordcloud" +version = "1.9.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/04/a3d3c4b94a35586ddb97c6a3c508913159161cd558b34f315b382b924bf7/wordcloud-1.9.6.tar.gz", hash = "sha256:df17c468ff903bd0aba4f87c6540745d13a4931220dd4937cb363ad85a4771b9", size = 27563741, upload-time = "2026-01-22T02:08:52.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/c8/ff2453f332f7e61bfaebefb1b1967c4f05cee15c3b1e5f3edea36fb7c351/wordcloud-1.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:41dfe1c6b30f731225ce67697b0589df5539032b82a4c334f94ead3151d589aa", size = 169057, upload-time = "2026-01-22T02:07:36.597Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/cd17e9ff9014c5328e3191c7832d18bd3b010eb25a493dd2ec7ca2cf6296/wordcloud-1.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c4677c4b70e800ce3ff84b904db2ab9a30a6ad03898c668a71507da9ce14ad7", size = 168470, upload-time = "2026-01-22T02:07:38.063Z" }, + { url = "https://files.pythonhosted.org/packages/1d/55/583ad9135080934f391f76f97c80ea68f0e85ef667f7297cc55a5ae05bb1/wordcloud-1.9.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b200b26bc746fa97c52dc8eba2bdca95c95366ddca51df167bd35a16c48a678", size = 522915, upload-time = "2026-01-22T02:07:39.409Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/6d3b313d98012b1a6e8f194a0116e6f92f7d30934cf7d7848db59989c969/wordcloud-1.9.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:682fd4bfc2f0c262c1ccf2bbc0ed503361f40c3dcd2e3dbe9ec3c233916cada6", size = 526203, upload-time = "2026-01-22T02:07:40.58Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5b/e5b1b492ae795345c4984b7944cd5c5db00d95b35b0b74d1d39388e311fa/wordcloud-1.9.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f41dcda0ca3a5b3c220ee9a928db7fe3b6bcfc534e5b9fcdac8afebb59a18f1", size = 518357, upload-time = "2026-01-22T02:07:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/54/0b/7d0d433dc309ac82f15a911bb3f9f7bea05e9560861586b8cc5e498bc148/wordcloud-1.9.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d63b1dcd3bc3c5f68ef591f454cae13c0d613a91b86db3d62f5f420b333fbedb", size = 529531, upload-time = "2026-01-22T02:07:43.928Z" }, + { url = "https://files.pythonhosted.org/packages/29/14/e822d6b96b9552b97dd3db8231ef57d33b63325eca4c3eb39499ff56707d/wordcloud-1.9.6-cp310-cp310-win32.whl", hash = "sha256:5746286fb0506fd9731ec35c25046e6437db9f1e85155cdba113ac184625b3f8", size = 295856, upload-time = "2026-01-22T02:07:45.352Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2a/16bab06c328f1b0fd4c0067ebcb73f31cc1c7dd3a5699f99fb8bf8e4530a/wordcloud-1.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:2c9702fa654c9305d1bff86043147ee19661da145218e7b0114cb4f6d1459462", size = 306215, upload-time = "2026-01-22T02:07:46.635Z" }, + { url = "https://files.pythonhosted.org/packages/54/6b/369ba57a28b4233ff517eb18633e9f0b35f0b9851afe2a0dcb84b05739d3/wordcloud-1.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6eab5eb4caa4bda125dd3acc8f71697617c26c2a2203d8fcdaf2a92a7d12a4a8", size = 168799, upload-time = "2026-01-22T02:07:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e9/259b1ea381d866bc56963945d494da1589a64cda5443d3989fe8926548ab/wordcloud-1.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc9ac1ad23ef76c1d1fbbeafbfb6125d2d63a9346bac642ca201cbc457da8f8a", size = 168419, upload-time = "2026-01-22T02:07:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/6f/84/bb64813cd1ffc255a9d8f4a4faf9daa283e9ad0ac127366df716ee2b1719/wordcloud-1.9.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e3c214381244043d5921ba653ccf7e7407c3f97915dc99c639ebc4acd47dce", size = 547809, upload-time = "2026-01-22T02:07:51.077Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/773bcc351664eb980ea36e56e8f1ecb62ce657e0936b9971e66f17065819/wordcloud-1.9.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1c31741b7612d9f408551434d4164e75d2e6b8542b59ec53ea77f773286713a", size = 551314, upload-time = "2026-01-22T02:07:52.151Z" }, + { url = "https://files.pythonhosted.org/packages/93/62/4f4e6ae70bad5ff39fff4cbfd97b7a6e0807db8557ea1faf09643a828cf3/wordcloud-1.9.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec394e02202e84550f50ab88cba5683f54775f89998dd46b8e41e297a9f1735c", size = 544345, upload-time = "2026-01-22T02:07:53.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/44/2ab3357b1f161e317c83f4e179a576f9c247c1cce41a9bef1fe89e01e6b1/wordcloud-1.9.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc80e742a95af65bae9556b5ec77837a104b12aeadb4772dedbd352f9dfc3e7a", size = 555174, upload-time = "2026-01-22T02:07:55.256Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c9/663e9468b34a402e0861b5c2a2eeb4d71e5c60a81c35b2f4dcc9a9af8649/wordcloud-1.9.6-cp311-cp311-win32.whl", hash = "sha256:c96ba0b5d7194322e88bc2bfbaf653cd3de9a5dbe3f78e07ea365ac40cd2f987", size = 295601, upload-time = "2026-01-22T02:07:56.271Z" }, + { url = "https://files.pythonhosted.org/packages/45/70/0041966d469dec79036ad3962b83b007004b842531ee7c41bdba61310eb6/wordcloud-1.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:8a1b3b15509e05c1c3322a205108f7da31ca06bbcf979c104e1a7b9b9b76fff2", size = 306051, upload-time = "2026-01-22T02:07:57.662Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/1df77d67d1cc990f83b70708b002fc8378779c94b5d0a80e570c5ead04b2/wordcloud-1.9.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e51ebaeb6ce337b26ce4ba7e5eb3359981a8a648713301a252f49dbab5fe56cb", size = 170137, upload-time = "2026-01-22T02:07:59.172Z" }, + { url = "https://files.pythonhosted.org/packages/04/72/1aeb291fd5965826e478b0efd8bcb4351e8a2434f366416537096cd41a0d/wordcloud-1.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba607e25f7ab78085e6c7a9b3d9cb5eb637e73560e5a8b4f6924705d64a76b0e", size = 168932, upload-time = "2026-01-22T02:08:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/f2/28/a011d949b6cba617a6aaf31994afc81d38a467510bc76be4e96a37808a62/wordcloud-1.9.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fce2f0fb0469623db85e1e974ea64f51e78758c4d8e84ed0b4344530d1ba8ab", size = 547540, upload-time = "2026-01-22T02:08:01.744Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b9/916484ac803dbdbcd0f8669a6363264a438801feff938d5f3f209521ee2b/wordcloud-1.9.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f534204038811676890bc91c10bcca0b04c6933011c250b4b09323d2a0b0a8c1", size = 554869, upload-time = "2026-01-22T02:08:03.054Z" }, + { url = "https://files.pythonhosted.org/packages/d0/84/a1e23051927588e9567da232adfd54485a7def7957bb23287b89398e5050/wordcloud-1.9.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dfea5803c6e2b540da04f9693da93fd90d9babeed284950ab720487eb7b44942", size = 538205, upload-time = "2026-01-22T02:08:04.293Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ae/4926fa61265cd492ee504ec1ac9880b8840eda2c104c06e57f516f883f90/wordcloud-1.9.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11b55fbcb2fa5db7e876887858fdf6480f21300b4384ff610c3aa9c0ef420ae9", size = 554626, upload-time = "2026-01-22T02:08:05.363Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/f388ba4a7ec09a2d2c2c0f1cb2cbf158fe5850aa1f03831e854782a31c03/wordcloud-1.9.6-cp312-cp312-win32.whl", hash = "sha256:1200af0c9be744c9e70fd7305c80d4b317fbcb1d41cf9b72a24d648e70ad598c", size = 296150, upload-time = "2026-01-22T02:08:06.423Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/47d0d8c5ca74400750797ae8fd13f200204294e008e1235e51814e732b09/wordcloud-1.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:7977a1727e059d6ba0a679dacbab57a966ab28913fc1764079efdfdc67f8e4d2", size = 307222, upload-time = "2026-01-22T02:08:07.786Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/067c0a7c75db885c573b80834deb16b63a8a145146916438b640439eaa46/wordcloud-1.9.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:706d19a085170151b1deade56fcc1b367e809b4ebea76f2ee39ecb95d45b2fcc", size = 169349, upload-time = "2026-01-22T02:08:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/65/bd/54e8ef889a73f47ac0216b8acc774bf7b260dbad4cd0a62f8638d43730ca/wordcloud-1.9.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec02d1a44040d32a21acdeaf3fe7d1c4f4ad6d42fd417b13c7c60b41403c6978", size = 168343, upload-time = "2026-01-22T02:08:10.055Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/e306dc81577e540b14e88f44fae111a3dd2542f04bcc770660a22b03da7a/wordcloud-1.9.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:122d01c617cac7a6620acdcf182ff117df1e08dc31d1947ac669c5af1946c9bf", size = 543612, upload-time = "2026-01-22T02:08:11.829Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/b70f403be0a6fb722fd6654b0905d0b4914fec6a5c5a11525715dc5facd9/wordcloud-1.9.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cad69c24ae6b3f33eae5ebff012ad6c5dd7e79c0f6f768646676722281acf8f", size = 550736, upload-time = "2026-01-22T02:08:13.383Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1d/1c5aef5da9a90fb4859ca014d4068a5d5d2a310868e2f94b0c1cbac965fa/wordcloud-1.9.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f5fef15420e4fd1c3ba18766cd7225f6310a780602798cdd35046b004e6adb8", size = 536274, upload-time = "2026-01-22T02:08:14.467Z" }, + { url = "https://files.pythonhosted.org/packages/23/af/fb4e76467a8c992d873da67e2e2fef6d63d47d2424fcb426ba7c28dadee3/wordcloud-1.9.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b82ee3bf54d1f324346c7eebe4d0146748b913e790ddb661679d7559fcc0769", size = 551984, upload-time = "2026-01-22T02:08:16.499Z" }, + { url = "https://files.pythonhosted.org/packages/0c/94/1a9760be65dcd2f9152a36b3935182d748d06195c5cd535baf4498fc8d94/wordcloud-1.9.6-cp313-cp313-win32.whl", hash = "sha256:3a245498f429b4b37e909e5410887da8aed697f1d8b70c0a6d7f37ac7188654d", size = 296019, upload-time = "2026-01-22T02:08:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4a/a9cd73b01af02fa84265cf35e19ce91c31f9f7c538325115fe258bf0ada3/wordcloud-1.9.6-cp313-cp313-win_amd64.whl", hash = "sha256:3d3c5b0b5f66a385300dacb5ba2c2dba67c18c332b934fdac08261c1c7ee7d7a", size = 306987, upload-time = "2026-01-22T02:08:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a4/da39308bffd24e82761d804797f04d428011b4bb3be51135177a5b884842/wordcloud-1.9.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5cd785127483b835d22c7e7a235fbd925d01fcd2846c2eacf45d715e32775563", size = 169670, upload-time = "2026-01-22T02:08:21.127Z" }, + { url = "https://files.pythonhosted.org/packages/64/72/a703bd2fbc79fa6ae78aaee34d01e24d0324e5874c0a7918c73f27857f5c/wordcloud-1.9.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f9bfe99fde048e109343858a7a866bdccd95f70cabe171aa7fd9fb7609bff12b", size = 168911, upload-time = "2026-01-22T02:08:22.238Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f8/fc2b3f5689a91aeab55bd47f0022371e41ee41e2a705eebbc2a0981a8c60/wordcloud-1.9.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfc67c14d5af51a8ddf2a36be85f4ef017c835f3f37a11ab7ef1a898c950f8b4", size = 542948, upload-time = "2026-01-22T02:08:23.548Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cf/f15a13027b0d976ebcbb1c1f3c0a52aaa93a06a84952859e12d0cb7079f8/wordcloud-1.9.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2998a9a6ea9dc18c81a980403d03ed97822c971bd971b2faa3b5ee2da047f237", size = 546260, upload-time = "2026-01-22T02:08:24.612Z" }, + { url = "https://files.pythonhosted.org/packages/59/f5/290bd0b7e039f3b94e9961fff6acabcb761bef27d0a65516423e014bbfec/wordcloud-1.9.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e11c048f9056a9dda20627c29c578cb34f6778b152d0f0eb0dd33581faf03f65", size = 535535, upload-time = "2026-01-22T02:08:25.828Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/6eb7bae66bc2b34e4e2f5bd1c5cae8ddc789255a163c15a816b602519fc2/wordcloud-1.9.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aaaebdc056052fc3f21e3aebc6653fc76ee6c4fdcd5785a9991ce186eb15a776", size = 548616, upload-time = "2026-01-22T02:08:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/63/89/8001d176085d6d31b107b58c29b7648898d6af9cb23b9d76d67d83cf88f3/wordcloud-1.9.6-cp314-cp314-win32.whl", hash = "sha256:eabd94c69435b19163991da8e71310bb4873e31982e54ff4cf62317f0e1223b0", size = 297137, upload-time = "2026-01-22T02:08:28.391Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/b9ff7ab3dc030cbf7b2737adc5eddc847b99c8665a45007b25e558cfff8b/wordcloud-1.9.6-cp314-cp314-win_amd64.whl", hash = "sha256:8549f85a93626f5d03c06e63106ce228910008becd1e1f3b49693d13e33a5873", size = 308629, upload-time = "2026-01-22T02:08:29.662Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/2183079c0eca58a211f1456f524bcb283fafc65f1ccae54f412b07efab52/wordcloud-1.9.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a4070ef896396d3fbb7a71c775c08138d61b85ea4d414e86b4e132f8736f20f6", size = 174014, upload-time = "2026-01-22T02:08:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e1/397cb2e0e2c9424841ace579edbe96d133291496f8312f24d70a855b36d3/wordcloud-1.9.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:436b99261ef31369019b989137a940835de887de52dce5b63786ef13fb82e18a", size = 174152, upload-time = "2026-01-22T02:08:32.053Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5c/a0570972c5951c6586dbe9b25b87914f5376406b4da9ec877bce9b0fcf47/wordcloud-1.9.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4804bee501b85b6f6015c434879a3e9ef2795b97c00aec387a0d6adfcaa2ca5", size = 559434, upload-time = "2026-01-22T02:08:33.192Z" }, + { url = "https://files.pythonhosted.org/packages/89/00/0d5d6731c98312c5ceef83dbdc50a34479b63377c8258e17b613e37fead0/wordcloud-1.9.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33a4b3dcbd9095a968dd3201bc6667d9788ec7b949f6d8356985a8fc64a0590b", size = 551605, upload-time = "2026-01-22T02:08:34.633Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/7089b537fe791447abce3bbbf89edf9cd6585a25f80764bcf386b2a245b4/wordcloud-1.9.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74c9e5cb52c35aa822ff7f3251a633aa52b830ba206be275c1da743de255a15c", size = 542705, upload-time = "2026-01-22T02:08:35.726Z" }, + { url = "https://files.pythonhosted.org/packages/78/77/a14ab3680c08ca585b25c1549ca9b3ca52a078e82450ad513475d9edffbc/wordcloud-1.9.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:337b73155c60fc536cab8a998bd8312f81c99524716c86e3e43f3b6408f42bdc", size = 545800, upload-time = "2026-01-22T02:08:37.105Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a7/bb2bbc36739472e8328dbe4246c5b7593b3fa4f8b77a1214dc3ad8e0a7fd/wordcloud-1.9.6-cp314-cp314t-win32.whl", hash = "sha256:32f93e42b44dca992eb5f692ddd258f043465f49d9a53e6aa3d4853fca615e23", size = 306383, upload-time = "2026-01-22T02:08:38.627Z" }, + { url = "https://files.pythonhosted.org/packages/8c/fd/2704f0be5f4913c623b283a1c92016b9ce93cab5ea0f6e86e8517c617c32/wordcloud-1.9.6-cp314-cp314t-win_amd64.whl", hash = "sha256:22cf91490bcc0fa23585acbab1906a44a438fa7dd4d9a2b2663f39c8650634a6", size = 320391, upload-time = "2026-01-22T02:08:40.094Z" }, +] + +[[package]] +name = "wrapt" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/31/5822ce37ca8820c2ed35a498c67c8b37960b9cee2ba437fd32849d0a234c/wrapt-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0bb2797048db0956348cb3058c33bc4184614f13231389cfbccc16a5d32780a7", size = 81191, upload-time = "2026-07-28T06:04:04.858Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5a/3c6117938be98754578ab83f5a40d7d0ea2cd2c487dc5cd6027ee7228229/wrapt-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce9f398f868d2b3b27aa2ea4de79645ef9077aeeac8dfc2814b0d542c6a2b87f", size = 82255, upload-time = "2026-07-28T06:04:07.151Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0f/94ae724c5087eb6054c0d63febd7094947dcf302fe058e2e0488102a872b/wrapt-2.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad71df7a04dd3497e9302e81f4a7c91bd401ea0e15a9df9029527900f94bee43", size = 155228, upload-time = "2026-07-28T06:04:08.272Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/1f780bba935dcf697c0c59de9be3a559bbb8e31a53ca3f25422023738432/wrapt-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc82c2ccc8e234c844f5303d9f2984b346dcdd53e94823ce8420d2c75b4b9023", size = 157073, upload-time = "2026-07-28T06:04:09.459Z" }, + { url = "https://files.pythonhosted.org/packages/73/31/6c7799d7b6431fcd7e1b83245fb45258a2d2c3a2187fbaecb83572a72d7a/wrapt-2.3.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6e19531ae33c508cea7d84a7edfda01fa86e51b8d1a93a77712c55e6e469152", size = 151594, upload-time = "2026-07-28T06:04:10.784Z" }, + { url = "https://files.pythonhosted.org/packages/ce/17/42d670dbfafd49076c6eb2b7d67633d7e1c968e39bfb11a135acb6fac67b/wrapt-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df4ce31150bcd5d9f36f816aac3010ab4f4bf8672ac1d3b0ac7d539ec61c7c02", size = 156069, upload-time = "2026-07-28T06:04:12.316Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d6/c66b4ba4eda49257c84d5c2df26118280f09ca7905aee20d0064db778d13/wrapt-2.3.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e2e692bc0d63f881cf7006730a56bd4e0c2fab5dc318466942805d692b166276", size = 150930, upload-time = "2026-07-28T06:04:13.482Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f2/1a3b949c0322fb27396eafd1044328c1cb0400e0b32105d75a3cd03096e7/wrapt-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c8388ba7faf5dbf9ee106bb70d66f257629b1bd98091123e19e8a4553a319199", size = 154525, upload-time = "2026-07-28T06:04:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/147563a3dfa6e830c857b93b530ebd8c0cd9d540e5914aec8f9b12880c02/wrapt-2.3.0-cp310-cp310-win32.whl", hash = "sha256:e045ff75d7d94900fc32896ed93c45ce2d2cac28c9dead582ff9a5a49d446e35", size = 77879, upload-time = "2026-07-28T06:04:16.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/eb/921405b4dc55d4f8be4c700ef120539fdd75d5fdb50d83bd257171ee18e0/wrapt-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4fc96b159af0a3e0faa72475a69d66292bea72a5bed1e1aca1bffbddc3cb2b0", size = 80733, upload-time = "2026-07-28T06:04:17.43Z" }, + { url = "https://files.pythonhosted.org/packages/b6/13/75947450c5bb57795fa86384721cd52c5c4deb0879022f309501a8a85d44/wrapt-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:1236fa25173ca964c97422470482e9011b9e3c7ed0d75798b40b3da3b0e0e760", size = 80199, upload-time = "2026-07-28T06:04:18.761Z" }, + { url = "https://files.pythonhosted.org/packages/00/b8/9182e4c618a847be0baccb68e4602b070d0fa22c782cf058f4bc66b32709/wrapt-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ab559e1b2551d23d54db2a0001c6d73bad022a254639561c5f6c382a9d6c2fe", size = 81427, upload-time = "2026-07-28T06:04:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/613cefd9c5977366b1587e61c0b428176d382e6d75b454084c5e58503042/wrapt-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bff9a671bc00709cab5a7f745c592b5671873449db0ee2a569af994f16b29a4d", size = 82360, upload-time = "2026-07-28T06:04:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/4cd2151a236f44a6e2dd4ed8011838d7ba0be3d656c8bafdfc65a2ed1917/wrapt-2.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc648a335d7e01adb3640b25f02fd0ea05886cf04d0af7f4ee902bc7b5e466e8", size = 161700, upload-time = "2026-07-28T06:04:22.723Z" }, + { url = "https://files.pythonhosted.org/packages/49/2c/bc508fee75eb2919ed69769800b09968e4aab16897f909a23f39c81e323f/wrapt-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0077f3d65541925fa83002f967b22ad6550d24813ac64cb905f717194128d9c", size = 162922, upload-time = "2026-07-28T06:04:24.177Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e5/04f34d38e66d857dfc2fc4088d60e70c0e422467822defa49b2b4a26e17b/wrapt-2.3.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9790ea25190a4e0fe4cdf4eeb868e9d75f8a024a70a5b6bf9c348a3a2b72e731", size = 156125, upload-time = "2026-07-28T06:04:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/c35940ea1c423f129ebe4361db853bc80d4def6326242e1206fa15bf94f4/wrapt-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:816877aa749253149f9ecfd2635d4d948ecfa338e1a0311d187b1acb1bb8a3eb", size = 162039, upload-time = "2026-07-28T06:04:27.154Z" }, + { url = "https://files.pythonhosted.org/packages/0e/60/9bda34c3d7d182aa703fe35339ae0ed4c4dad5e5c587f93890143e1f87fb/wrapt-2.3.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d1c2c1b808600d2ea808e6360910a60ed5f409a4011655e10f9164ba0a414a6", size = 155110, upload-time = "2026-07-28T06:04:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/60bfd9b1a751f4fcb2d603668fc272d651ccdd339a56acf8c40ad21a0293/wrapt-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ba1e5e08ddc46130e9682b2c249f2d1dd39bda9106ed4bd401b7519f18f41bd", size = 161089, upload-time = "2026-07-28T06:04:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/0f/32/2bd358c6f4f1305c813479d1e9ba746bebdd794f4a20107ab2b3ee0cbd45/wrapt-2.3.0-cp311-cp311-win32.whl", hash = "sha256:45c9279b373d15649dfa2c2077cb3408ea1a6d3125afbdab9d6b809a66f68e14", size = 78030, upload-time = "2026-07-28T06:04:31.241Z" }, + { url = "https://files.pythonhosted.org/packages/4a/62/ecc969b13b141fef89b888c9760821cb01a86ac8fc953911592c8e1e1522/wrapt-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:195b1842b4122fb54e3cd3dd5b2b4aa49302a5a61da901df0481f5c97aedde84", size = 80944, upload-time = "2026-07-28T06:04:32.655Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3d/9278ada8a2b3f24372b630361e84e9a7de7abc3784634860c26d1c37785a/wrapt-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:6db604ef0c67bdb2042ecdfd7b7f037cf09733557ca42360d1018285634f7b98", size = 80074, upload-time = "2026-07-28T06:04:33.811Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, + { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, + { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/0f88a072483e76b881e3fdcd6b6ffb4a5791002514fe541e72b1b73c859a/wrapt-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d3fb71e65b001adfc42684522eeccd9c21d8ba679945abc993439567b66e59f", size = 81960, upload-time = "2026-07-28T06:04:49.622Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ff/b7e2776e7c294075eb712cc9ef573d1b818f393006d09787262b8fc871c4/wrapt-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51a7a4181c1295774812271fbcd7c909df372bc25579d4ed9eb875caaf0ae86f", size = 82435, upload-time = "2026-07-28T06:04:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/343bb5d0f1f9669bc252a6073f085b4abf862511bd5c9c9eaec754341f1d/wrapt-2.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9045917809c63fdf7abe3a2ceaed3d670b8ee4500ddd9291192d30aeb34467c5", size = 170350, upload-time = "2026-07-28T06:04:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/59/f8/13b79a392930bd0dd6b86cbfbfe1c40944110456e1dc6d809e5c46ece904/wrapt-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54ca1d5573f69b5fe1d74f1f65799c68015e82f685efec9fd8cfa40a094c44d0", size = 170022, upload-time = "2026-07-28T06:04:53.599Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/4f1b6918f5290db959d6e0c07f77385d87cede29c39c9cf8f145e9c82954/wrapt-2.3.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:242b60c21e30866e6a2fa606c612b47c553fa60c0eaeeeb7797fb842ac0ce609", size = 161043, upload-time = "2026-07-28T06:04:54.936Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/45d3cf74414780bdff6d0380467e003f6eb0f028b6c9403db868dbc7209c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3f3d7ec0a51fbfe00d3aef047641ff2c58b25565b4717fc1f90e050be01cba8", size = 168576, upload-time = "2026-07-28T06:04:56.261Z" }, + { url = "https://files.pythonhosted.org/packages/f3/73/2fa58dd97f191c997755e2c6d569a68f0c433db4e4b36099bdd7227b6cac/wrapt-2.3.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:261f53870cd4fb2bf38f9f972c56c728fd224cb7c65721307de59d9e7e6741ae", size = 159140, upload-time = "2026-07-28T06:04:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/29/a8/08a56e2000a8816d449dcbad8c8b081697acbbd490821ceca0f9d8e8d20c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8159ec0b0cb7608175eb150de94c19e34f4d47ac655f5ca9baf45df6b688ffd3", size = 169263, upload-time = "2026-07-28T06:04:59.161Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/354e1725e35a73b2af4fa70a3e024c7a5d1bf1802dfb862dcb668aae0253/wrapt-2.3.0-cp313-cp313-win32.whl", hash = "sha256:10461884b3014fbfc8eb7d09a93c5f246363e6711d9d881f95eb8c27fdef049f", size = 78241, upload-time = "2026-07-28T06:05:00.507Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7e/34c87fa2174848dfee820322aaa318bab08913998ccecc8d2f57b4ad4639/wrapt-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac870cc97b73bb00ac353329e9559a4bebc47c4c86792ed9b23b58c15b6ad838", size = 81113, upload-time = "2026-07-28T06:05:01.839Z" }, + { url = "https://files.pythonhosted.org/packages/11/86/fcc9a530579e008c9478bb565a6cdfbfd33536660f069c8b91a6607c5050/wrapt-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a65e8db2b4e90c2e7ade931086351c98ef420bf7a94ee08c95ac8a3cbbc43579", size = 80182, upload-time = "2026-07-28T06:05:03.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/50/3864848b95b28ef73e17551fc8dccbff2628a834f52cf26a57f9c419fb83/wrapt-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fd1f2f557dd3491fe75905e578f4db967393d40d1a8f468edc4d40ac7f2d5944", size = 83921, upload-time = "2026-07-28T06:05:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4c/3d1921a60c3e8c71c540ff136e6a47a1fbccf7f671e818394889f7871d9c/wrapt-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9f5d2aec29dfc76c37e23897dee92766a3fd4f3bff3ae7fc9c6b4bf37d8c1360", size = 84412, upload-time = "2026-07-28T06:05:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1a/4a796ff7adb26ada6d4b758c94d47a38320b085e7099afc088efbbcdb006/wrapt-2.3.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:646d20d413ffcd1b0a2f700076e2d0252d872dcb7754860a73e45a59ea883614", size = 207168, upload-time = "2026-07-28T06:05:07.256Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3e/d7777776806c579b761bac2f91721dda9f04c7a1b380213c5935cc750ae6/wrapt-2.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:379f670f45b7bb8993edd9f6fc36c6cc65edb81cffa0b504be34acb0303fff0a", size = 214351, upload-time = "2026-07-28T06:05:08.945Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/2d64d394df7bf181955b3bb562bf33c4492fb4be113f53071106d43ad8b5/wrapt-2.3.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6208f302f110295d64b22a7ac96500c791bf492dce4366e622e4912b077c9687", size = 199020, upload-time = "2026-07-28T06:05:10.418Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3d/fb31d3db7d9834d265fb1a27a2adf0ddf51557c67458c97b22439ad6ae3d/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed635a9ca4f3a5a2b900c10c69e823373bc00ebc114b459383596d3487da3570", size = 209969, upload-time = "2026-07-28T06:05:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d1/8724b5da582e62070dc9bf4d8bf1972f317297eefd7ba1f2b5c6393ccf6c/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e3b9eaa742ae7a0aaaaad4ca4b69469d757af2d6e6663ef1dadc47adec0aeb41", size = 196324, upload-time = "2026-07-28T06:05:13.557Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/3d9ef411149543016ee6bcf3af707f787cebd946527452b94bf122e9b7b4/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0f7284f88f4833705132d06d3b425a43095c2cbd07c58166aac3ab646ba12a4", size = 202610, upload-time = "2026-07-28T06:05:15.048Z" }, + { url = "https://files.pythonhosted.org/packages/13/9b/4fc042ceb757866dd4a5fc057b3b736f2b360d3703ce9f830d83dc9226e0/wrapt-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:7ebb274aba688b043429eb1500ff8a76ce0cb8ac0812ca3e301f06247b8722b3", size = 79178, upload-time = "2026-07-28T06:05:16.469Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/b94878f8eed809ca042685276bcea9f24e8c2ca7c9653bb80bbb920a68a5/wrapt-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c4bded758ad6f03b965830944a2f0bc5b2eb3767fe5a7310134315d1a6610e98", size = 82634, upload-time = "2026-07-28T06:05:18.026Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/663e1de5332a71685a729754312d327d4cada767c36e1c5a2db4c8de49e6/wrapt-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:d2cc64539da63e39ffb9c7ede849b6e8ddaaf7b3876b5cfb04efd85a5f3f4eb6", size = 81387, upload-time = "2026-07-28T06:05:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/58/10/b073beaea89bc0d3670a75ff51139430a54b6af7ba7796507730634536dd/wrapt-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea52a0d0f08c584943d5764be0e84efa912c8da23c23e1e285ff2f5641c18fcc", size = 81978, upload-time = "2026-07-28T06:05:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/0916d9cebf848ed3f1a0c1888faee421747df77331e4db2bc527a9a85988/wrapt-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd85b0aa88efdb189d6ae2f35f4526943a8f091c38599c9c31478241c819e6a1", size = 82518, upload-time = "2026-07-28T06:05:22.562Z" }, + { url = "https://files.pythonhosted.org/packages/f5/73/31c1bf0f3384062751c2094dadb314916d70aa9b6bfd26d994b4a7b393fa/wrapt-2.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:141ed6211286a9660d8d6702de598b43f0934b4f0eda16393f100a80f501d945", size = 170187, upload-time = "2026-07-28T06:05:23.904Z" }, + { url = "https://files.pythonhosted.org/packages/ed/25/fce087d54b79b8905f3c3c9dd5f454bbd8d8acb80b960c4a6aee5b4659b3/wrapt-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e49885a62ec4ee854d1b9e6371fda6afd219917225752abf729a3f36d4df9a5", size = 169288, upload-time = "2026-07-28T06:05:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/0d09e6dddc6b7a7230ac77f50254b5980ab4fcd22976f72f8cc8a0404458/wrapt-2.3.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d6159c9b2fefec02314e1332dbbbfaf960e369dfd26bcf7f8b258b5732065b3", size = 160932, upload-time = "2026-07-28T06:05:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ca/0913af0d2ec0c43865d32d615f518fea66c13c5c930e489e9b0de248e9a8/wrapt-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24da48596326ef8e448cfa837b454f638713d3531262375f00e5a9681682fc07", size = 169017, upload-time = "2026-07-28T06:05:28.501Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f2/3d1e47ea81b822210f5df1bf942fd90780a75c055243d569b664529dea88/wrapt-2.3.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cd3a2edf0427013736b8127955cec62608c56e53ea47e82812ea32059cda407f", size = 159065, upload-time = "2026-07-28T06:05:30.01Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/ef2066ced8e5fca204e2b361e9708e36555b40949c583d997ea3b590817d/wrapt-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0df3bff4e7ce45759f33fd39335fe2f60477bb9ecf7b8aa41e7d07ee36a23", size = 168821, upload-time = "2026-07-28T06:05:31.649Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/016104650d4e572fa91506eb396b3dd8efbccc9284fdc1c9479c3d21db28/wrapt-2.3.0-cp314-cp314-win32.whl", hash = "sha256:2935d5454b3f179a29b12cf390ee47246740ba2c3a7545b1b46ba31a5f2a4a0b", size = 78700, upload-time = "2026-07-28T06:05:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/6fdc20a9f2ca304748b3f0819cbf377d55260562777bf0b615431bc3c181/wrapt-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:cc2cea812e5cb179a796b766747e7d3b21088760d8deb95676d482b8c8e6fa7d", size = 81422, upload-time = "2026-07-28T06:05:34.774Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a4/9cbd53bf05746bea2c392af39cb052427a8ec95cbd494d930733d8f44681/wrapt-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:22cc5c0a717bd4da87018ae0bffd4c19c6fb679d3ff357216ba566ab26c76cab", size = 80639, upload-time = "2026-07-28T06:05:36.228Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/6c5e4a0f66ea0d2b2dd267e8dd05a0014eea56840b3c8595d40b0a5d1f91/wrapt-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a6b5984cd65dd639546f0eb4b8eacf1c31cb2fe9fb5c27bffe240987cdb2cf84", size = 84030, upload-time = "2026-07-28T06:05:37.714Z" }, + { url = "https://files.pythonhosted.org/packages/6a/eb/a1aedf03283bc9cbf8a1783995ddc54e3c5a86878f19002d2c428494f4c5/wrapt-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c88abcf53daef80e01a75c7530e727fa6e2c1888fe83e3dcdba4c96216a1f5c7", size = 84419, upload-time = "2026-07-28T06:05:39.131Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/50d511c0dc5105563849e86daa3e16ac7feef699f79fb05af45ea70107d5/wrapt-2.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85de890ff968196e92dd1ae73a9fb8970495e7650a457b1c9ef0ac3dd550bce2", size = 207171, upload-time = "2026-07-28T06:05:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/9b538cf7795217e810699d16bc88b96a830d9b5c403eb2ec2db6b5f2ae81/wrapt-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50f416b74d092bb9f41b424e90dd457f365f7ba4b11de62a23679769a21bd85c", size = 214329, upload-time = "2026-07-28T06:05:42.287Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/9935d62b1499e5c8b3d191e99ba4eb31ca237a0b699142011a837e9dc7ea/wrapt-2.3.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39febbee6d77301d31da6996b152ce52452da7c7ef72aba10c2fa976dff9c295", size = 199079, upload-time = "2026-07-28T06:05:43.958Z" }, + { url = "https://files.pythonhosted.org/packages/2b/01/4446b80fa2ffa47a3449b250d004ba1c1937f07f64a179608fec735df866/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93513bec052c6cd987f9f580c3df068c8bc4ebae6543736be3ca7ec5959cafcd", size = 209992, upload-time = "2026-07-28T06:05:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/d4/07/56f26c9f9979586a021e8148747004aba4498f49458c90b0502969b904e1/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:729126e667da34d251b8ebf8a45ef0c5ddadc21542b3d6e1abf4259ece6508df", size = 196334, upload-time = "2026-07-28T06:05:47.608Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/6d7bcc895b0f28b2250e10908f060687b9165429dcd7f22ddb3d4c031b74/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:626b69db2021aa01671ec7bbc9740e558522bd44c18cf2ce69bf3d666a014109", size = 202644, upload-time = "2026-07-28T06:05:49.183Z" }, + { url = "https://files.pythonhosted.org/packages/cd/25/7860927edba06b758b8852a6f02e832be715563c67a6795d94350bc81099/wrapt-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:629d73378082c00a8173031f9fb30a3ac6abbc894a5bfdfae71fabc60642d501", size = 79685, upload-time = "2026-07-28T06:05:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0f/270bafe92fde3b069a39bc01e39ee79340895b335640df861d43d2a51885/wrapt-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:42869085687f0aefd57c0f636c3f9354f8ffb321a8ba9cb52d19beb796e561c5", size = 83104, upload-time = "2026-07-28T06:05:52.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/b3/af176d79a8515a8a720eccdad9a96f6e31a30abf2865430c8c42adf2fd13/wrapt-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1e5aa486e269b00ed35e64771c7d0ab8096cfd2643405ca8cd60ebedc099a51", size = 81774, upload-time = "2026-07-28T06:05:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, +] + +[[package]] +name = "xarray" +version = "2025.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/ec/e50d833518f10b0c24feb184b209bb6856f25b919ba8c1f89678b930b1cd/xarray-2025.6.1.tar.gz", hash = "sha256:a84f3f07544634a130d7dc615ae44175419f4c77957a7255161ed99c69c7c8b0", size = 3003185, upload-time = "2025-06-12T03:04:09.099Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/8a/6b50c1dd2260d407c1a499d47cf829f59f07007e0dcebafdabb24d1d26a5/xarray-2025.6.1-py3-none-any.whl", hash = "sha256:8b988b47f67a383bdc3b04c5db475cd165e580134c1f1943d52aee4a9c97651b", size = 1314739, upload-time = "2025-06-12T03:04:06.708Z" }, +] + +[[package]] +name = "xarray" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/96/3f7bdd00e505ec3698903415b30135024703e017b28c6b61f98da3193b0d/xarray-2026.7.0.tar.gz", hash = "sha256:361b495928fdbf5b58d0969bb6775339019da5e93ca74d61ddf4eb5edd6ce604", size = 3145348, upload-time = "2026-07-09T17:38:26.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/5b/28365212062939d213802e5e5fe855cdb231e1c2a93254ba1690066504e3/xarray-2026.7.0-py3-none-any.whl", hash = "sha256:bf9dd130b93806dc78e90c1b7ac24851b31557b888674c53622235691cf21824", size = 1426778, upload-time = "2026-07-09T17:38:24.224Z" }, +] + +[[package]] +name = "xarray-einstats" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/654cca0448ad5c1d0333530511bc20eefaab304a4362dcbdc7ea3da12a3d/xarray_einstats-0.8.0.tar.gz", hash = "sha256:7f1573f9bd4d60d6e7ed9fd27c4db39da51ec49bf8ba654d4602a139a6309d7f", size = 30225, upload-time = "2024-09-19T00:07:39.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/07/27f0d68989bb1c44a781747e222dda67cf65002834ed35ad91abd1a71802/xarray_einstats-0.8.0-py3-none-any.whl", hash = "sha256:fd00552c3fb5c859b1ebc7c88a97342d3bb93d14bba904c5a9b94a4f724b76b4", size = 32553, upload-time = "2024-09-19T00:07:37.904Z" }, +] + +[[package]] +name = "xarray-einstats" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/10/ef474494a7f2102ec4c02352c723fa282c6237b600565eb82ee354291211/xarray_einstats-0.9.1.tar.gz", hash = "sha256:39b373deed43592c41d3fbf8863af62e19e01c1ae553ae5ff059a8df78d995c6", size = 33327, upload-time = "2025-06-18T15:53:28.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/8b/ed2f0f49385c3d7739cd4699954add26e8f09a372a0c3f04f2bde32fcea2/xarray_einstats-0.9.1-py3-none-any.whl", hash = "sha256:777339524e85d066f2ef9ed1e3a3fb63aead4c1065fd1406f30dfa4de58ce063", size = 39043, upload-time = "2025-06-18T15:53:24.088Z" }, +] + +[[package]] +name = "xarray-einstats" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xarray", version = "2026.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/70/adb14007ee636eadd2a404802f7bb340c6f123ea81cfca152ebd4b6e2660/xarray_einstats-0.11.0.tar.gz", hash = "sha256:69f48a60f62151d40c49fd6d9a6dc0b970aecef2813a33209e4dcde897fca48b", size = 34290, upload-time = "2026-07-20T15:32:38.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/7b/34a9ce89f7a1e78423ca5a66492341c4f7daf6d0e9668af427fc2a7bc099/xarray_einstats-0.11.0-py3-none-any.whl", hash = "sha256:9dc8372aeb5db233054ddde4e57cbbcd1eee6c2e1b6bbe85863881c09b4421b3", size = 39828, upload-time = "2026-07-20T15:32:37.169Z" }, +] + +[[package]] +name = "xgboost" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version >= '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/bb/1eb0242409d22db725d7a88088e6cfd6556829fb0736f9ff69aa9f1e9455/xgboost-3.2.0.tar.gz", hash = "sha256:99b0e9a2a64896cdaf509c5e46372d336c692406646d20f2af505003c0c5d70d", size = 1263936, upload-time = "2026-02-10T11:03:05.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/49/6e4cdd877c24adf56cb3586bc96d93d4dcd780b5ea1efb32e1ee0de08bae/xgboost-3.2.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:2f661966d3e322536d9c448090a870fcba1e32ee5760c10b7c46bac7a342079a", size = 2507014, upload-time = "2026-02-10T10:50:57.44Z" }, + { url = "https://files.pythonhosted.org/packages/93/f1/c09ef1add609453aa3ba5bafcd0d1c1a805c1263c0b60138ec968f8ec296/xgboost-3.2.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:eabbd40d474b8dbf6cb3536325f9150b9e6f0db32d18de9914fb3227d0bef5b7", size = 2328527, upload-time = "2026-02-10T10:51:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/96/9f/d9914a7b8df842832850b1a18e5f47aaa071c217cdd1da2ae9deb291018b/xgboost-3.2.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:852eabc6d3b3702a59bf78dbfdcd1cb9c4d3a3b6e5ed1f8781d8b9512354fdd2", size = 131100954, upload-time = "2026-02-10T11:02:42.704Z" }, + { url = "https://files.pythonhosted.org/packages/79/98/679de17c2caa4fd3b0b4386ecf7377301702cb0afb22930a07c142fcb1d8/xgboost-3.2.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:99b4a6bbcb47212fec5cf5fbe12347215f073c08967431b0122cfbd1ee70312c", size = 131748579, upload-time = "2026-02-10T10:54:40.424Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1661dd114a914a67e3f7ab66fa1382e7599c2a8c340f314ad30a3e2b4d08/xgboost-3.2.0-py3-none-win_amd64.whl", hash = "sha256:0d169736fd836fc13646c7ab787167b3a8110351c2c6bc770c755ee1618f0442", size = 101681668, upload-time = "2026-02-10T10:59:31.202Z" }, +] + +[[package]] +name = "xgboost" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(python_full_version >= '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/41/846d4de2b8fc694073fd3ac5052caf68caa1ea11cb7fa32d7ad9c049b232/xgboost-3.3.0.tar.gz", hash = "sha256:58bcb8a4cace648cdab7b94fa4f16d2c9ff26d90dd4d26907168106fa06d8746", size = 1224702, upload-time = "2026-06-17T21:26:50.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/72/3b68983c0215ef65d48e9eeb1f168c3c6e3d62a61ece605de3209c79cae1/xgboost-3.3.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:07688a377046b8640897b62421150bf73c6cc7101823474ec6ad08b93290f587", size = 2553505, upload-time = "2026-06-17T21:21:32.146Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/b49e756822b29909d0c95ed334662dc6c7c81a99ec6bc10dc18e69f3d6e7/xgboost-3.3.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:af7cea10f418b7c251ddc8da440f57bdab2990b5fc9f74a35a92b0f150ea287d", size = 2376040, upload-time = "2026-06-17T21:22:01.981Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/a0adcd1ee28f525bd5c9dc3ebe78a7599bf97c22866d6449f967b829e338/xgboost-3.3.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:624a83aeb1e7ba081719795db179f4ce6fff12e79de05cd9baf15ee48fd22f0e", size = 98180629, upload-time = "2026-06-17T21:24:00.804Z" }, + { url = "https://files.pythonhosted.org/packages/47/1f/8b3e578cfd8e3bcdb4374e2bbe0b40b4e5320accb5cbdcf535ecc512eb5c/xgboost-3.3.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:f59edaf28eccd1c519788607c72ed907ee6cedfa933d706620bc1612d24b354e", size = 98716607, upload-time = "2026-06-17T21:26:21.058Z" }, + { url = "https://files.pythonhosted.org/packages/07/6b/087fd5d28fdbb90d385c50ee9308a820241b82feebdf42e72e19a48e4b32/xgboost-3.3.0-py3-none-win_amd64.whl", hash = "sha256:b06057f6a018fc04e6b3e0c15568ca636b8151a5b5f333478e500fcaf4fc7594", size = 69522696, upload-time = "2026-06-17T21:20:53.707Z" }, +] + +[[package]] +name = "xlrd" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, +] + +[[package]] +name = "xmltodict" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, +] + +[[package]] +name = "xxhash" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/63/71aa56b151a1b28770037a61bd4e461c2619cfc8866a4fcaf1548605e325/xxhash-3.8.1.tar.gz", hash = "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46", size = 86223, upload-time = "2026-07-06T10:49:58.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/97/1a8cebf0a6650417f08a18231590e2515aacd5ce39c3ad8b9e013ebd437d/xxhash-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:27a9e475157f7315826118e3f3127909a0fe25f1b43d3d3be9c584f9d265f937", size = 34695, upload-time = "2026-07-06T10:43:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cf/745b9bc0dd9c341bc074b5fc700db7bbef0f3b69ab21446492296ab37e50/xxhash-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b2ce44bf8f4a1d01f418b3110ff8dff32fd3f3e836c0e06333c3725f243fa6c", size = 32376, upload-time = "2026-07-06T10:43:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/8512a901b1d6ad4a9838d1b40385907a879d7e005a5afbec5d39526b69f6/xxhash-3.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:942bc86e9be6fdd6e1175048f5fe8f8fdaaf2309dd1323ef1e155a69cd346780", size = 217470, upload-time = "2026-07-06T10:43:43.572Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ad/0ffd8094ea29579bb2dc42fa74d08570e9ea3d95db561e6b1105e69b9ca6/xxhash-3.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0204701e6d01f64254e0e5ff4255812b1febe027ddd7dda63372e27f98b5e91f", size = 237799, upload-time = "2026-07-06T10:43:45.248Z" }, + { url = "https://files.pythonhosted.org/packages/b3/90/783c6b3f9336bd07449fe672be32cef6833633936bbfda8d3b23ee18d202/xxhash-3.8.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7dc4bdf008f77c88d544849c48c1a40faf25a5eff6cc466de2e8edc37c191fce", size = 262587, upload-time = "2026-07-06T10:43:46.733Z" }, + { url = "https://files.pythonhosted.org/packages/c4/77/ba0316a7c3e661b86830a47ae4987798616ce1b15af8d2a6358e2d89ef60/xxhash-3.8.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c566b123dce7e4867ca518434cdfb9f84e5023771235b2e3107a26c9a41cbd8", size = 238484, upload-time = "2026-07-06T10:43:48.453Z" }, + { url = "https://files.pythonhosted.org/packages/09/79/33001037c1cba90f4ced38b257161c13452024c0db44208f883e2e47f3fc/xxhash-3.8.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9f23083e1bd9d901f844af7a126727c486e7eada9a1a6791c8f7e73f94fac656", size = 469909, upload-time = "2026-07-06T10:43:50.188Z" }, + { url = "https://files.pythonhosted.org/packages/45/90/237eded9dd6ae638083294e5a9f77b317aaebd480a330806b39c192a0de1/xxhash-3.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64af54dd1c3a45a27c04942f9a1a4683322bdd127f4745cca4e02549c1d2d2bb", size = 217166, upload-time = "2026-07-06T10:43:51.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6a/8cb439dc9920e1468e1c2d69ef77cbeb4be3b1ae9f4b5344c07a2b59af18/xxhash-3.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8ea8a141eeced4f6262ab6dd71c681ac546a558c30bb586abe087d814b5f85ea", size = 307593, upload-time = "2026-07-06T10:43:53.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/c0607d373c8affea92101a3926c4fc8b026bcf8983e05fd58f3a0380ebf8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a98b2f95cab589e0f5e92c48431afb4d56238b8bf6668edcc66166180e9b509b", size = 234702, upload-time = "2026-07-06T10:43:55.042Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cb/f4cfd456624c1f017858168b7ba9443dad810da8aac779a612658450e827/xxhash-3.8.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1b86ae798a976ccbc1d02af6ccb98f5b4d24756b1f65e995f11d10fe071f486f", size = 265749, upload-time = "2026-07-06T10:43:56.749Z" }, + { url = "https://files.pythonhosted.org/packages/33/f3/9006669c04b01206e21b2177425c649461ba188930a052c2f1728d6ec6a8/xxhash-3.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81f4ed9ca9644bc95cd976bfe10f7a4cafab8ffdc3aed52877d4600e445be7ef", size = 221992, upload-time = "2026-07-06T10:43:58.12Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/7e6f3eaa05df5e0b6c94aa452b0672801f7031e602081f07fd441aaaaed5/xxhash-3.8.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:cb3fe820c27593f170770d6c8d791936cf6275d9269405fbb7b30a55363c10c8", size = 236899, upload-time = "2026-07-06T10:43:59.562Z" }, + { url = "https://files.pythonhosted.org/packages/da/cc/bbaee4987f3aab1d7b33bb430bb49e940646160af448b9167431c931126d/xxhash-3.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7345007c12780985de4fd740148776d1eee18c0d41407c6fa1e48c5450304fe5", size = 297934, upload-time = "2026-07-06T10:44:01.132Z" }, + { url = "https://files.pythonhosted.org/packages/a7/97/6bee358660eb8b4f73c00b00b00bc616ebde00e1ab4b67c63486ce360648/xxhash-3.8.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:12eaeaa9ab8b9e6033a1fa5f6b338aaf55ff4df4bee11b59fd6ee03b19186ee4", size = 439315, upload-time = "2026-07-06T10:44:02.878Z" }, + { url = "https://files.pythonhosted.org/packages/c6/50/7e35275f39256bedace0c3cd5be3c72d4ac9d5aecf5e5fdc3530337cd263/xxhash-3.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2a845687219ba3214126f14a8a5861f97c9e065a7d0b8252adb6df13eea86fb", size = 214038, upload-time = "2026-07-06T10:44:04.504Z" }, + { url = "https://files.pythonhosted.org/packages/59/2d/69d02d096ee50bdf3ef0d208d874f52c71b1aa6906066bce3c52fedb8bc6/xxhash-3.8.1-cp310-cp310-win32.whl", hash = "sha256:656256c9f9303e47f07d5cb8ae4468285370adfafd7ba48aea33a458e7697626", size = 31939, upload-time = "2026-07-06T10:44:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1d/e06fca9844919ca91c6587d530cfa1e745830ec73ad38f44f04b25d1bfb7/xxhash-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:27cfc2f1ed76f956f36dfe0c56e5f5a3e94cd91eb78b893f63e2ef2ae404fcdf", size = 32729, upload-time = "2026-07-06T10:44:07.621Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/800648d99039927b5a86d8ae02cd86a556a5ee1678d388216f6b44c8966c/xxhash-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:c85949d02c85adf6d786eb94858e124989a632a4e65739835b2fc5761827fac3", size = 29215, upload-time = "2026-07-06T10:44:08.916Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5a/05eaa129555f85476a3e16ff869e95f81a78bbe4647eef9d0229f515a317/xxhash-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147", size = 34699, upload-time = "2026-07-06T10:44:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/59/0df1133958b2228929355e022aab1e958c7b2c43e27bf7f59bc9edfa8a54/xxhash-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10", size = 32373, upload-time = "2026-07-06T10:44:11.353Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bf/1cfda5b5e6bf26617812b4a31662ef2220d2ad04e0a55b8ff9eb36e56a5c/xxhash-3.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670", size = 220284, upload-time = "2026-07-06T10:44:12.683Z" }, + { url = "https://files.pythonhosted.org/packages/70/93/45dc0ad7913b69e5b08bd039236cf628380e4c9cc76a8a4c6625a328e058/xxhash-3.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c332dd48b8cb050da2bb2a3c96d72b1664168650a250ef9718e423df7989e05", size = 240980, upload-time = "2026-07-06T10:44:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/f28ba7d17f2c1410ee397982c817ab1bd5b2701070c2d2c373539aad000a/xxhash-3.8.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5cd96f6dcdf4fa657b2d95668d71d58455248f98712ecffaa9c528edf40ccae", size = 264526, upload-time = "2026-07-06T10:44:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/f10651cec2c7981b20d693deae6bdfc438427d92be2db4ccabb6181f0021/xxhash-3.8.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c959f88160b13b4e730b0d75b459b7929fc0d2225c284c9683ac95d6feeeac6a", size = 241369, upload-time = "2026-07-06T10:44:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/136e0cbaf5db51e191423b1c98643593189f02b6cd90837bf64b19113d70/xxhash-3.8.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:027dee4355f3fcc41481650d846cf6cfc895c85a1ab7acd063063821a0df5b4c", size = 473186, upload-time = "2026-07-06T10:44:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3f/6aa808a96bdc43dba9a740dec56c744526ee3c0019e32c75e810fa90ae4d/xxhash-3.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad52a0e4bcc0ba956a953a169d1feec2734a64981d689e4fc8f490f7bf91af60", size = 220092, upload-time = "2026-07-06T10:44:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/47/28/a8675e78a9ced96dab853416162268e10e05b452e95db7888cf69f58ac5f/xxhash-3.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d3dfb1f0ff146da7952867a9414f0c7a29762f8825a84879592612fd6139342", size = 309846, upload-time = "2026-07-06T10:44:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/7fe4d4ef4e69f0033e012396ee2a115886bca7b10b7e45ce398626436bfc/xxhash-3.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4482380b462ca9e59994d072a877ecadd1cf51102daeeab2db696f96ab763723", size = 237659, upload-time = "2026-07-06T10:44:24.135Z" }, + { url = "https://files.pythonhosted.org/packages/38/8f/83e9e31d4ed57fe963b99cb5b13a23e3e0f0dad1885aa0ebd2a7819dd423/xxhash-3.8.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:950ac754d16daea42038f38e7465eb84cda4d08d7343c1c915771b29470f065a", size = 268737, upload-time = "2026-07-06T10:44:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/57/79/7e7de46dbe5d1f49afc96a0bc42e6b8df24eae3d6bad6007b99e42f48430/xxhash-3.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0418ec8b2331b9d4d575fc9284427e8e69449d7172e99e1a86fcdd1f51a0a937", size = 224955, upload-time = "2026-07-06T10:44:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/ec/34/b8540839e958d5ef5c6101af6f16032109e7099698ae8edbc8dcefe4d8f4/xxhash-3.8.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:32a94ad2763e0263d9102037d349002c3d3c401e42770542c3eeb4801f311661", size = 239653, upload-time = "2026-07-06T10:44:29.422Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/a735d05f7f859354acadabe470ff40e2c46672275f96dcf096a761904def/xxhash-3.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:89b11a5cdd441aa463f6d34ca0241602bc09b001a76994b6059828494108c673", size = 300213, upload-time = "2026-07-06T10:44:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/3e1cb020237b68117fc212dc5f9753b87f865b4dfee7c1ce62d0836955b5/xxhash-3.8.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:09a204dd4bb0823daf938cdd0dc8057d5f1e14fe3cbde929424255f23f9de872", size = 442508, upload-time = "2026-07-06T10:44:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/f80090622141cc734b039ce1d15ce3ff6dced375e9680249bf5b9b8c6bf9/xxhash-3.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e710ad822c493fb80a4fbc1e3d0a807b1422cb90adbe64378f98291b7fa48fef", size = 216853, upload-time = "2026-07-06T10:44:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/60157acecc307b238d3651c2483168e224b48b23a36ae6d6903588341d80/xxhash-3.8.1-cp311-cp311-win32.whl", hash = "sha256:5013be3bea7612852c62a7437f3302c1cfb91ca7e703b194459db0b2b2e0d792", size = 31936, upload-time = "2026-07-06T10:44:36.542Z" }, + { url = "https://files.pythonhosted.org/packages/59/5c/ef70c418d878d187b8da56d4cdc06aea6cf5e456b301e96e51e1d2cc8625/xxhash-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:f377012b86c0a23a1df0cf5a1b05aa7187649e472f71c7892e5f2c2815bbe74f", size = 32724, upload-time = "2026-07-06T10:44:38.177Z" }, + { url = "https://files.pythonhosted.org/packages/2c/25/f008db952cec6b2a26445b456eeed2ebebd65e08e848ebe09ed6ac0634e6/xxhash-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:836f11d4474d3228e9909d97216faa4f7505df41cfaf3927eb29809de785a78d", size = 29212, upload-time = "2026-07-06T10:44:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/91/f65c34a7aa7b4e7cf4854f8e6ef3f7ee32ceac41d4f008da0780db0612f6/xxhash-3.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e6e49370822c1f4d8d90e678b06dbcb08b51a026a7c4b55479e7d467f2e813bc", size = 34680, upload-time = "2026-07-06T10:44:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/57/04/b10a245a4c09a9cfa88f8e9ae755029413ad1ac17047f9a61906e5ae0799/xxhash-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:220d68130f83f7cc86d6edfdeab176adc73d7200bf3a8ec10c629e8cf605c215", size = 32397, upload-time = "2026-07-06T10:44:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/45ab795b5945b6388583bd75202106af505537935566c15a1577797a0e08/xxhash-3.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d365ee1892c1fa803536f8c6ce21d24b29c9718ec75eb856095c07830f8c478", size = 220549, upload-time = "2026-07-06T10:44:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/13/44/5ba2bd0a14ddf4193fc7d8ec29625f659f22c06d60b28f04bf46305d8330/xxhash-3.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:852bfe059720632e2f16a6a4745e41d20937b2bf2a42a401e2412046bb6971cc", size = 241186, upload-time = "2026-07-06T10:44:45.534Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/c4147def4d1e4538b906f82731e0ba23424377fc50a7cddd03cd284c8f63/xxhash-3.8.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f8c25a7061d952de589bd0ea0eaadee32378ff83dd6a677b267f9cd86f401f8", size = 264852, upload-time = "2026-07-06T10:44:47.199Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/71ed14f4f0318bb7fd7b2ec51999413487fa8da8d41208e84d50d1ef0f98/xxhash-3.8.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:868a8dcaff1a84ba78038e1cef14fc88ccf84d9b4d12ea604696e0693296aa56", size = 242663, upload-time = "2026-07-06T10:44:48.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/09/70af22c565a8473b3f2ae73f88e7721af281bc4a575236dbd1970c9f76f6/xxhash-3.8.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6536d8677d2fff7e64cd0b98b976df9de7aee0e69590044c2af5f51b76b7a170", size = 473510, upload-time = "2026-07-06T10:44:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/34db781c8f0cf99c544ca1f2bc2e5bf55426e1eb4ca6de8ea5da56a9f352/xxhash-3.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c0cedd280eab2e8291270e6c04894dbc096f8159a39dcf1807429f026ca3cc", size = 220469, upload-time = "2026-07-06T10:44:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/9a184f615fa5a4dce30c01534f62946ce5a11ce40f73785cbd356ccabaa9/xxhash-3.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daa86e4b68221d38e669bb236ba112d0335353829fb627c82e5909e4bbe8694c", size = 310290, upload-time = "2026-07-06T10:44:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/a9/dc/9b9a9789011ee153723a5eb9e7dd7fcbae2ba9b3fe7a729249ca7c252056/xxhash-3.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2bc7113e6f2b6b3922dd61796ca9f36af09da3773898e7003038dc992fc83b8d", size = 238173, upload-time = "2026-07-06T10:44:55.693Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/71c6005ada9dcb608a4e1902e8475ecadb5f3fbfa04e1e244d276a2d0c43/xxhash-3.8.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5eed32dad81d6ba8e62dc7b9ffa0500199385d7810a8dd9d4eafaceb8c6e20bb", size = 269026, upload-time = "2026-07-06T10:44:57.424Z" }, + { url = "https://files.pythonhosted.org/packages/2f/87/d6c036ba25dfbd9c8633be5aa86fc9474bbb9e2c68212a841d090abe7344/xxhash-3.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:83697b0ea1f10e7f5d8b26a4906fa851393c61546c63839643a2b7fe2d868061", size = 224970, upload-time = "2026-07-06T10:44:59.085Z" }, + { url = "https://files.pythonhosted.org/packages/48/62/4c1f035a41c5752aa05e195b6c904c07b94fe9061a16de61e72a6e6b135f/xxhash-3.8.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36fc69160465ae75c6ec4ac9f781bb2aa16ae7ff869e73c26fee85fbb11b9887", size = 240820, upload-time = "2026-07-06T10:45:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/da/14/d39d565069b87e86d21a2af2a31d04db79249d25aa8d5b62959056a89857/xxhash-3.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:445e0f5a31f2f3546ae0895d4811e159518cdc9d824c11419898d40cfadb677e", size = 300619, upload-time = "2026-07-06T10:45:02.716Z" }, + { url = "https://files.pythonhosted.org/packages/13/22/75467acc887edc8cf71c97ab1708feb3df7a88bda589b9f399765c6387d2/xxhash-3.8.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:dfe0580fbfd5e4af87d0cc52d2044f155d55ebd8c8a93568758a2ea7d8e15975", size = 443267, upload-time = "2026-07-06T10:45:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b6/1da3baa5fa6ef705e3425fddd382be7dfc4dfba2686df90a20f16e9c7b1b/xxhash-3.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:095e1323fa108be1292c54c86da3ef3c7a7dc015b105a52133973bc07a6ad11a", size = 217338, upload-time = "2026-07-06T10:45:06.304Z" }, + { url = "https://files.pythonhosted.org/packages/78/dd/b5295a9f97484e7a1c2b283a742ca45e3104991c55a1ef670dde161829ba/xxhash-3.8.1-cp312-cp312-win32.whl", hash = "sha256:bf28f55e427e0483acb1f666bd0d869b6d5e5a716680c216ad7befe3d4cfba2e", size = 31970, upload-time = "2026-07-06T10:45:07.823Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/3fa0b807d7e21515cd975e7fe5c039d52ac3e9401a96d6ad68dae6305215/xxhash-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:2256e80e4960ee282f63428adb349cb7f8bd8efe4db770d88eb815f4b9860724", size = 32741, upload-time = "2026-07-06T10:45:09.42Z" }, + { url = "https://files.pythonhosted.org/packages/b8/05/86feada74e239600e6875aa507afb40482a89b92700aa74a92da83bdcb77/xxhash-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:9df56e6df96a60590935e22373041cccc91fd55858763dcffb55bf63b3a2b396", size = 29234, upload-time = "2026-07-06T10:45:10.809Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/446bb782cd0d27007a917b5569a08dd73219c3e8d6e459014db104b27bdb/xxhash-3.8.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:3c682fcd96eb4bf64be32a4d95f96107e1588005831bd8a741b324fdda01b913", size = 38562, upload-time = "2026-07-06T10:45:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ec/c0c45627eaa6be7a5d6117423adf8f7a15b17ee74b4b17072cca5959a225/xxhash-3.8.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:036a024d8b9c01f70782e09ed98d532e76fd23f950ae7154bd950fe94e90ebec", size = 36656, upload-time = "2026-07-06T10:45:13.932Z" }, + { url = "https://files.pythonhosted.org/packages/f6/94/8324c04cc7597154caaeba6c094e01fbd2e7601d01e7a13eea9f5420e77b/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d6a5c0bce213b23b0166fe0d35bcbbe23ce4b968f257cc7eb6fd57cb8e1e6297", size = 31169, upload-time = "2026-07-06T10:45:15.687Z" }, + { url = "https://files.pythonhosted.org/packages/40/a4/beb6bb26e1184e126dbe7a5682330214ef54dcfbf882078aa9f4b5428d42/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5177aa44eddaa97c6ef0cc00c6d540edb64d51781d2f8fb941612ec61a92c9ed", size = 32177, upload-time = "2026-07-06T10:45:17.035Z" }, + { url = "https://files.pythonhosted.org/packages/56/0f/fc4c92a5a528f839b34b6419b2e53c8597f2a629d5a1f5d721f65bfa1fd6/xxhash-3.8.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7801b7223db017b9c0c9ccf37e44524edb35a1544a1c032add22c061c6af0276", size = 34642, upload-time = "2026-07-06T10:45:18.39Z" }, + { url = "https://files.pythonhosted.org/packages/d4/58/edbfb141d4000767ac6a9694f8ac0763e2c2e983e65c9e31620ba56e2667/xxhash-3.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e80238259655bf69d7bcd08226a970d7f42605f3157786bfa76dd13472d7fa0", size = 34684, upload-time = "2026-07-06T10:45:20.033Z" }, + { url = "https://files.pythonhosted.org/packages/07/3f/5072f1f0f5714186f0ac2a0b5a4929ce30d4b845e94886b6c01b6ebda0be/xxhash-3.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcab50a389cc04d87f90092af78a6adba2ab3deca63175a3344ca83514045315", size = 32401, upload-time = "2026-07-06T10:45:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/49/c7/802ea2f9c2ed59219934d6d65c470d502b1788043eae277a52af8658bda6/xxhash-3.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2489d3a776fa380cb8e71f54c7fda268a9baf3de9b1395093fd280f95735907", size = 220617, upload-time = "2026-07-06T10:45:23.234Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/e10488efd31fcb13fcd6acbc6e788f10c6f8e3a0cc4ae3eb89dc19c55a12/xxhash-3.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ab1e5432690276e71192be7401b55f96db2d0eedea5d44eb1f164505669cc0", size = 241295, upload-time = "2026-07-06T10:45:25.364Z" }, + { url = "https://files.pythonhosted.org/packages/18/cc/14180b17d44892a631f8ae7323c30bfbb1328efc8209e528a480293528ac/xxhash-3.8.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b30e01a0b97a4bc3f519a4d7a82da3dc53251fb0de5eeea8660dcd4ff094c0c2", size = 264688, upload-time = "2026-07-06T10:45:27.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/72/a14019d0c5f6c41ee407a503036ae32787c91325ca218a96a9b5627be651/xxhash-3.8.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f44275ddb0978b67a58a951501903f04d49335a91f7681c9ce122ecb8ccb329", size = 242740, upload-time = "2026-07-06T10:45:28.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/92550e556c6fcfcb96c6a336945eb53a431ed43120ed749636debb16c5cf/xxhash-3.8.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3b87cbd974512c0c5fc7b469c36b2cdc9ee6d76e4ec78bccb2c7184611c49b0", size = 473599, upload-time = "2026-07-06T10:45:30.524Z" }, + { url = "https://files.pythonhosted.org/packages/29/83/e361d3c1acd1b21e1d489616de6fa4aaf843365d8179f612e3743eac20a9/xxhash-3.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98ee81b4b7f3023c9cb04a78cc67610baffcb5812d92f2096cb5a5efc6f19437", size = 220559, upload-time = "2026-07-06T10:45:32.979Z" }, + { url = "https://files.pythonhosted.org/packages/05/01/006a4243c2c2a6831827f9999f6d1c23feeef100eb023c1f886022a00bf3/xxhash-3.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2666f059a1588a99267e33605365ed89cea92f424b3522806a9f4bd8ad2e3d62", size = 310383, upload-time = "2026-07-06T10:45:35.875Z" }, + { url = "https://files.pythonhosted.org/packages/d8/20/af388e8bf9f9a0f89eeef7d2a1935d176ee1c20bc6adeda05035879379cf/xxhash-3.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0093cf7eeb91b84776e8742113afa4bdf47533d36cf719179aaaf1f56f6f8bf", size = 238228, upload-time = "2026-07-06T10:45:38.02Z" }, + { url = "https://files.pythonhosted.org/packages/63/6b/4666579a87eebd1744663c404297355fa0658617b015cedfa58810ee7036/xxhash-3.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3a800912a2e5e975d4128969d645c4a2a80aa886ccd6c9b1c6f44529e327e8cf", size = 269137, upload-time = "2026-07-06T10:45:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/de/d3/e963a8a46f900a137d91b02144d8ea07a8f812971b138204a3b2f8b8e55c/xxhash-3.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0fe37f72a207223d22a4eddc3149d4298993385aa9daef25c039246ca5a309f3", size = 225068, upload-time = "2026-07-06T10:45:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/aa/80/9d181dbcde4b0fe48375f48833a5832d4b8cd2b349b15110c92ee472d874/xxhash-3.8.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5db43f249b4be9f99ef4b967863f37094fb40e67effafb78ba4f0356b6396104", size = 240874, upload-time = "2026-07-06T10:45:43.414Z" }, + { url = "https://files.pythonhosted.org/packages/39/15/ce3ab5a1cd27ead25a5196e55a7284220f6ad6e316da494ffd900b2b600f/xxhash-3.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c4ed42965c2cd9081f011be22f69d0e65d3b6165fe7734072fd0c232840bbd4e", size = 300702, upload-time = "2026-07-06T10:45:45.135Z" }, + { url = "https://files.pythonhosted.org/packages/96/c0/2281a8ab5f2a62dbf57a23c58a01ccc1d98abf40f71193c8a81f59e759b5/xxhash-3.8.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3557bec8fcb11738a8920eeb68974bc76b75262f6947998d3147954ce0a4b893", size = 443351, upload-time = "2026-07-06T10:45:47.188Z" }, + { url = "https://files.pythonhosted.org/packages/81/2e/071a58c1a53a52d4f7a3aa0987be0c396dffd40da8204805fe1b130a81f4/xxhash-3.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00de40f3b42240db23a82a5c682b55d7263d84a26a953240c1aee463409660e3", size = 217396, upload-time = "2026-07-06T10:45:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/68/44/36ab58134badd9d3433fc7b53c4ca8d113d8e807782885628640f8297a4d/xxhash-3.8.1-cp313-cp313-win32.whl", hash = "sha256:b5196cc2574cfec572a5f3fb7cfa5ade27305ae3d06516a082132441aff4c83a", size = 31974, upload-time = "2026-07-06T10:45:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/2a0b84798448e766f7b89ceed073cb0cb5a43fc9ebbacbdea74a38de18e3/xxhash-3.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:538f5f865df6cd8c32dd63158a0e5b4f5dd08d732a7da8b7228a5a0776c8ce55", size = 32739, upload-time = "2026-07-06T10:45:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/bb51dbf7c363ff88a7cbd50b7959718219577ef44d7cf255929ffc4a2194/xxhash-3.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6617f30641ba0d8baa1635fbefb1dffc5165ec36d26921bd5cee13497cd937a", size = 29239, upload-time = "2026-07-06T10:45:53.714Z" }, + { url = "https://files.pythonhosted.org/packages/56/d3/827ca123c2ee5443a6aaed3c5dd199237dc2f010e2bebd7ec09ef36f3a5f/xxhash-3.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:bfcd82852c62a60e314670a9602de354c4460f8adad916e2e42a20860c7870bc", size = 34964, upload-time = "2026-07-06T10:45:55.535Z" }, + { url = "https://files.pythonhosted.org/packages/05/67/67ae2a3ccdeb8b8ef025d35aee9edd1d26c3abe5051d47da9286232afbf8/xxhash-3.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:08ea2081f5e88615fec8622a9f87fbe21b8ea58d88cfc02163ca11026ee62a92", size = 32697, upload-time = "2026-07-06T10:45:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/3d3994346e1f45493679cb5c1ffc2bf454e410e9d1e8a662d253becee91e/xxhash-3.8.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2e32855b6f9e5b18f449e59d45e3d5778bdeb660632ef2693cca267a11246c75", size = 225954, upload-time = "2026-07-06T10:45:58.897Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2c/53169270309b7cd8e05504e07fe123bac053b89d00ac63617faacf0a2ec0/xxhash-3.8.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6e088bd7870775624256a0d84c2a6714afd223b2eeb56b0ca58398e52a32fda", size = 249776, upload-time = "2026-07-06T10:46:00.977Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/5c551d8d592f944506f7c5185e210255c15e672a3c6008c156a1bd9b775e/xxhash-3.8.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:72eb5ae575cc7ae2b23f6f8064a8b10f638c7149819ae9cc6d20ebd4d37a1629", size = 274776, upload-time = "2026-07-06T10:46:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/d3a762270cee2d7bcd0e25e28c623e5f3f5c0dc637b66e3e47dd5b0bb3f0/xxhash-3.8.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d0b48cdf690a64cedf7258c3dc9506cc41fc86edd7739c40e3098952265dc068", size = 252056, upload-time = "2026-07-06T10:46:04.688Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/b78e4373b2cb6d1c42af60ea2d7e9146ad0710b239ac7f706d5d31d5bb98/xxhash-3.8.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb9e256a357dfcede7818c6d34e70db2d6b664394803d1de4b6984d2de76c0f1", size = 482108, upload-time = "2026-07-06T10:46:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/642d923336ea61a15f8ce64fc7e078729e6e06c3a026e517fa79b2c23b7a/xxhash-3.8.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51f71a6e2ad071e70c937e41fcb6c19f82c3f9f49831eba850ed4a106ffbb647", size = 226739, upload-time = "2026-07-06T10:46:08.598Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0a/a37d6da6427d45a8d23e3ee3a0ca9c9d4a90364849c6637fe2963a755f9b/xxhash-3.8.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4a6443968c4e8dc69967e12776776a5952c119cc1bd94168ad1c5ad667c2be1", size = 319658, upload-time = "2026-07-06T10:46:10.504Z" }, + { url = "https://files.pythonhosted.org/packages/4a/51/ebbd40da8a3f1bc53b4b7a9a87f8e28bd95c5f21bc14b8a57860cf367d1b/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:714503083a1f2065c9ad15340dd49ac8a8e948a505a705ffa1750cb951519113", size = 246059, upload-time = "2026-07-06T10:46:12.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/4c/d9014030147e1f0bb26e7da47aa240dd9ec61c763c573e558111d869f8e1/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:77f74e45a1e5574bbbf80181c8027b3a4c65c2248fffbd557bd596fff13102f9", size = 275535, upload-time = "2026-07-06T10:46:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/caee2db41fadcd5a25aa4323213f9afec5a8586d4e419241e3d659362bd7/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e0e1b0fb0259c1b75d1251ac0bb4d7ab675d36f7a6bf4ba6aa630dae94f9ffa", size = 231292, upload-time = "2026-07-06T10:46:16.452Z" }, + { url = "https://files.pythonhosted.org/packages/0b/60/f52f08bcdc904c4514ea5c25caa19e9f3214144434a6ff96dc82dc1cbddd/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:10e4393ec33633c2f05ad01869e546ad080b1a18f2650503731f153774608b31", size = 250490, upload-time = "2026-07-06T10:46:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/24/a0/94dc7ae310838f250669c6ad7168e6d6fca17d49dac1053f06dc232c4a56/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b3ba794c3d885803db6c3116686923f1ec13bc86e621e169a375282b63ea1cc6", size = 309861, upload-time = "2026-07-06T10:46:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f9/adeead7d0eb28cdfc2832544ea639ffbc6749ccde47a8e228d667459182e/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:57189a69c0891e4818853feaa521c972d22c880a001453addea015f48e3c3398", size = 448739, upload-time = "2026-07-06T10:46:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/22ec0e07db57d901c9298ae98aa3cf2be45bafded6f07c13131e85b89032/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d59e71153fe9ff85648d00e18649b07e9b22c797291abb7e27274fa06df8b838", size = 223657, upload-time = "2026-07-06T10:46:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/94/32/8a9531f37b59e5a013003db7cb7414baf4ce7e0e1268e0d5947cd3d6a2df/xxhash-3.8.1-cp313-cp313t-win32.whl", hash = "sha256:5b96f0024e9840f449bd91b2d005c921a4b666055a0d1b6492463799f32aae22", size = 32377, upload-time = "2026-07-06T10:46:26.86Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/2ca45fd7f671de5f81fc297ef1c95080b40c86ec6be0cc6034b8f7707ac8/xxhash-3.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:37d5a56c36dcc0b9a87b814cd992598d33863ff683749de6c86081f278d5e629", size = 33274, upload-time = "2026-07-06T10:46:28.39Z" }, + { url = "https://files.pythonhosted.org/packages/5a/54/20d7163463ddb6438b73a427d1655a77a502cf9b9b0c3ada3599629d9c0a/xxhash-3.8.1-cp313-cp313t-win_arm64.whl", hash = "sha256:6696c8752aded28ff3b16f33ef28ce28fb5d209b80c206746f943199fcf5fd65", size = 29375, upload-time = "2026-07-06T10:46:29.962Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/df2ba04f22a6cd6b39f96a6577329a8471a55c90ef8d8e2f7c102363613f/xxhash-3.8.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:9db455cb649dcfe4504d6d68a6d83a7315a99a3ca59871dc3ff840671f99adba", size = 38430, upload-time = "2026-07-06T10:46:31.496Z" }, + { url = "https://files.pythonhosted.org/packages/b2/4f/6a059e8ad3ca8deedc91dfe335b211204900895152212c03ebbe721de68b/xxhash-3.8.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:affb37f152e55b5e4494bb9d0107f7bb08515c6704fbed82d9f61214d74adc17", size = 36558, upload-time = "2026-07-06T10:46:33.078Z" }, + { url = "https://files.pythonhosted.org/packages/cb/95/40be178205acce092ae418feb20ac737b32a02c7b864926ed0717354c9f8/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:460261045936975193bfd20549a0de1cd52a33b405cbb972f0d80940c42266cd", size = 31181, upload-time = "2026-07-06T10:46:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/2da4dbf051bafa156c0e3f12012db2b0ac3b84ff37ca1f021f6bfffcdfbb/xxhash-3.8.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38c887aedb696ef8bca19983206d270848558cfae4a91afa6a2fb05dde58ffc5", size = 32192, upload-time = "2026-07-06T10:46:36.393Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4e/e000bbae3566bc8e0be771a8a0f294aa99075e3f0bc4ef43922ebffdebc8/xxhash-3.8.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:594131ce1aad18db3689781f806db1b065cdaa04f4df36b4c038d2013aefd0bf", size = 34691, upload-time = "2026-07-06T10:46:38.1Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4a/ea954aacc7d1c8711880ac2b55da94429a9b4296b151c4fc0966549ca1ee/xxhash-3.8.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:78c794b643d214f1522e7a288bcf5a2de120d26cd170516749a4009dc92722c9", size = 34807, upload-time = "2026-07-06T10:46:39.647Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/df598e738ff37558ac627264deb2e560902d9bf7f46d3bd5175c9eee593e/xxhash-3.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af0c9fedc4a2c24e8664953882fe8185f3790b8338c9c700f76f5ad660817711", size = 32410, upload-time = "2026-07-06T10:46:41.359Z" }, + { url = "https://files.pythonhosted.org/packages/59/9c/81ab40e7d33ada0b3df5d1bc884894d15dbf4f805cd645b685e4606bb8e0/xxhash-3.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:115772daeb71b2f3b9381177017f53e6cf3f3439c840737fdabd21aba6e54920", size = 220564, upload-time = "2026-07-06T10:46:43.463Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6f/62ae6f5c8606320a0e2a41c2dc8c6d91cc5d63d0f84dd9582e9543779dd8/xxhash-3.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:000435984a0469b0f822fe76f35bddea0f96a4d6521b3339a60a6428cdee1edc", size = 241462, upload-time = "2026-07-06T10:46:45.509Z" }, + { url = "https://files.pythonhosted.org/packages/15/a1/9c3a0ec6cb524396f551eddd102a76690a795494eb9784fc67542b0daa37/xxhash-3.8.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f1c68394818e0595569c2ff3cbc1e6d5a36a434e796f5c526b987b80c8a8c62", size = 264491, upload-time = "2026-07-06T10:46:47.655Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/700a4674e4308eb59d2fdb973977e82eae231bea5044753fee5c9eec0e0c/xxhash-3.8.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:46b39976d008e2a845758650f0ff7136bca004f40da0c8798bd37ac37860154f", size = 242905, upload-time = "2026-07-06T10:46:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8a/72d9874375c8d4cbc64a8cd1d659d5695a8765c3db82efa82dc5bd9f14d0/xxhash-3.8.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d5006c65ec507a333479e76e00e2c368781f16c24ededa764763956b32a0e93e", size = 473873, upload-time = "2026-07-06T10:46:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/03/f0/6db07590ed7e0a77f186ef0bcea8d52553bf1ba57833e09467a2411f0f2d/xxhash-3.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31a2649bcf1fe97cf11c79848d761df33ac46b3896942d31b640557b486ff6b", size = 220765, upload-time = "2026-07-06T10:46:55.41Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/00d12d8b8beabbf49a8bbc626fb9f40445145a8887eb41a6acfb69149ac4/xxhash-3.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f759eed402448c2bdbb492e4fba1f20668ffe29688605ea61f0f67f9e4e386d", size = 310478, upload-time = "2026-07-06T10:46:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f9/12a82394eefb0f185d15a7f7b9f627c61c475a72dd83718436a5b84b42ac/xxhash-3.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5f97ecfede10d5b2870383620e2d25c8561e217c7bf9081073802b54248d2b", size = 238393, upload-time = "2026-07-06T10:46:59.87Z" }, + { url = "https://files.pythonhosted.org/packages/20/f3/53f963e320b9ce678337aa7273f39ce692ded8b99e3d22a866ec722159ab/xxhash-3.8.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1da930bbcac3e8fbe2191850e2abb57977a99348c12c4b385e1058ac1b0a9ecc", size = 268704, upload-time = "2026-07-06T10:47:01.806Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/5b5badbd87c82d9f9b5f58ac74a3f29ef08f6fc387b324b8fd482450b862/xxhash-3.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:747476436f6891b9773374ce8d48edcc8b12cb5b61b67c6fb6289633747d088f", size = 225015, upload-time = "2026-07-06T10:47:03.784Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/3ca68265afe7b4e69435e08a7b6a1d9d0f2a071e889da1f8041ed00fe878/xxhash-3.8.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef09bbc2519a93cd0f95f2ceb5f7b85919dffea643278e02362bf40e3c4bed1", size = 240951, upload-time = "2026-07-06T10:47:05.816Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/27e19670c40f46b5e76e11f2f4713d21054804568425d870670e757172ad/xxhash-3.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a5eed9d41995a83f3332b4e3396abb7f433cac584222bd7e305b606d8353861e", size = 300751, upload-time = "2026-07-06T10:47:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fb/b33e27689959fe7ed2ae0b830af41560d65213943983afa9db3a8d481bce/xxhash-3.8.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:53f3ed9118397074ff63a79b66b7fec1c84c782eecde35c5bc94e420a971c231", size = 443480, upload-time = "2026-07-06T10:47:10Z" }, + { url = "https://files.pythonhosted.org/packages/26/60/0e0d973be5fe280753ef02fbc89349492ad6e903bf1dcb870b668f94b662/xxhash-3.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d247b34bf433c92b41689318fd25d246313cab2275a6a47e2efac178b80d6efe", size = 217657, upload-time = "2026-07-06T10:47:12.196Z" }, + { url = "https://files.pythonhosted.org/packages/ad/68/c9e3ecef4a9a417d464cb5bd200aa12f73192dee677901b9e08e0ad0d1bb/xxhash-3.8.1-cp314-cp314-win32.whl", hash = "sha256:d58ce8b6cfa9c4d2f230557f69caf7c06369e318015d0b19485095bc2c5963ab", size = 32690, upload-time = "2026-07-06T10:47:14.204Z" }, + { url = "https://files.pythonhosted.org/packages/d7/99/e9e44588c0b62837bbec5ba7927816de0afa03406b1a0b6c7a7e1d1a30a0/xxhash-3.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:6cee733fe4ccb1737e0997135283c82341e5cfa9cf214b165f9087fb663aaf4f", size = 33460, upload-time = "2026-07-06T10:47:16.021Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/64f36d86380b3657ad9031967ab814f3ef31307174650853f69c18932ebc/xxhash-3.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:58346024d47e84f7d8b3e7f5d6faa1d58acbbe49a8771497872059f58c1d8ea5", size = 30092, upload-time = "2026-07-06T10:47:17.81Z" }, + { url = "https://files.pythonhosted.org/packages/92/cb/18b64bff88c58a0ca209dc533e63cf02d7ae5aa6b1b9a9fd14e81b5dbd60/xxhash-3.8.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:01cab782f8a0a05ecad2c63d7ef10f7ab475f660e0d6419d069418c14d88de7c", size = 35024, upload-time = "2026-07-06T10:47:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/af/1d/72d8a70520e5dcddb472ea0486d299da3240745a10658290cd7b5690ede2/xxhash-3.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:717b12fdc51819833704e85e6926d76981ffa3f780ef92e33ebb8b26d46bb230", size = 32697, upload-time = "2026-07-06T10:47:21.649Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b8/e041f555903c56db3d0a731b3d72a6575d75e0ed868b1bd2e5176111ca44/xxhash-3.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ec55d80e9b8a519d742669e0b49e8ce9e6747be42bf3c138158b6543a9c8e489", size = 226044, upload-time = "2026-07-06T10:47:23.612Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7e/5cdcf06bf6ec4b5d2ac073feb23432ec1d603fd438864cbd2c09c7cb45e1/xxhash-3.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98d8ac1129b4dd39098cffed94d1284aceb61c3aa396757ccc736ac392e4cee5", size = 249899, upload-time = "2026-07-06T10:47:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c0/eb7e059cb5e1dba11fd30d2fdf882f56e5a417a3eaa43669d43623767f45/xxhash-3.8.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3bc0fa90830df1e1277f33cc6e55de9990b83c0319fd8c7412866cfde38b025e", size = 274892, upload-time = "2026-07-06T10:47:27.931Z" }, + { url = "https://files.pythonhosted.org/packages/66/74/a600aaf7cd39957fd1510adeedb1749c1e7eb82bd632a1153d9c664c3135/xxhash-3.8.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c73b6f652f0745425aa6378319c331293b5341756262e9408ed3d45f183375e6", size = 252243, upload-time = "2026-07-06T10:47:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/ad/04/78d88fa75a6763e5d09bf1b947a392a27988903381b219006f92f3c68fc8/xxhash-3.8.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6114692261eff4266386cdec0f7d87eee24e317ab397c218b7ae6a76b4c6339", size = 482191, upload-time = "2026-07-06T10:47:32.45Z" }, + { url = "https://files.pythonhosted.org/packages/7f/06/07a8aea1108d682de8791ce608cdf367d75ff4e7e57cd3c154bdc6f47b23/xxhash-3.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df57c0b161ec1b3ed0526a67b0db0914b557e86ee8aae51887aec941b261542", size = 226877, upload-time = "2026-07-06T10:47:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b5/86bade5618a524d2c06c4041aa2fe8e5749ce16e88afba60d67c1684a21f/xxhash-3.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9043877a917be88ccf230aa5667c1bd059bce80f4c2727e4defa1b29b7f48b08", size = 319794, upload-time = "2026-07-06T10:47:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/23/69/9b1a2b89b1621bb740fbcb7beb512f60f99480c1bdc680c0c90e1f56ff75/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:559e3cabe522231909f9de98ef06929edbd53782046bd21aae0c72db6f2a0775", size = 246202, upload-time = "2026-07-06T10:47:39.676Z" }, + { url = "https://files.pythonhosted.org/packages/08/ea/662ed6cb49f1d34078b6a3a3e0f3d29ff93fd7b5a03c0bc9ecfd9b2159c3/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:264710bd335016f303763ce1275c6486df30bb57c2245c91b224c983d7ac39b8", size = 275628, upload-time = "2026-07-06T10:47:41.99Z" }, + { url = "https://files.pythonhosted.org/packages/13/f5/49fc9e4c6728a5a3bd8fe639199d2fa67609b3a84f938aff6e8568dd3e4f/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e14800b9b10bb39d7a60ad4a310e403164d7b8988a27ae933d4e40618a44088e", size = 231390, upload-time = "2026-07-06T10:47:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/64/9d/3acaf8f599c0e0b30e910a3a11ba32929da53c86dc73c7c55fe6a010b4e9/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ea6a3e734b0fd41b82784a400be946821900daebe610c050a5e0760838a34f99", size = 250600, upload-time = "2026-07-06T10:47:47.611Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/8acab4c5ec60dbe664b5b9858fd44c2413b07e535b09556a0a5022e78aa6/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cf399fac542a1c7a4734a435b93df2c55e858c7d31abf6c1bdf46f9ae67fbfd0", size = 310032, upload-time = "2026-07-06T10:47:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/56/47/a0288d7329b1fe63e2734a32d19d444a96ae2b4810f545bc61e561224917/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:44c89d915a75c11d2547eaee9098fcd80398987c4bff2974a0497a925bf92c07", size = 448882, upload-time = "2026-07-06T10:47:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/01/e7/3071dfd3beb5c38204ce1cf56bf7749fce08de900fa92714b81d1d8ca1f2/xxhash-3.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:358650d5bda9c635da699c53adf4e8134af492ecc79c960f917eebf088bb6799", size = 223728, upload-time = "2026-07-06T10:47:55.093Z" }, + { url = "https://files.pythonhosted.org/packages/12/11/b99949f0ba2b07e9f9ffe83b9c86faa685f9080725dc21a916a607313be5/xxhash-3.8.1-cp314-cp314t-win32.whl", hash = "sha256:c240939e963653054fc7e4a17c382829cda4aa88a7daf0af841715dbded1b497", size = 33150, upload-time = "2026-07-06T10:47:57.274Z" }, + { url = "https://files.pythonhosted.org/packages/54/1c/09703eb341f8416e74e58d6c6732d4b5c46de59c942363203cb237cc95b0/xxhash-3.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:7258ee276e8772599bc19e14b36f6260306e21b637190cd7cb489a2449d48684", size = 34005, upload-time = "2026-07-06T10:47:59.434Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/6ed7251bb6a8af10ac73b1821c60583d2826e5b2064e45a979c935287c98/xxhash-3.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:8f454166c2ffed45636c8d501741e649851ba2f346c4eb73a64c07ac00428f20", size = 30239, upload-time = "2026-07-06T10:48:01.874Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/4d8040435aeac814fc69ba63621565fbeb19229a138e2568324a26b2a45c/xxhash-3.8.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39c9d5b61508b0bb68f29e54546de0ed2a74943c6a18585535a7e37356f1dd12", size = 32687, upload-time = "2026-07-06T10:49:42.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/975f1f2318c760e5bcec109ed379713ae645d8d856c2a3b9ec5d26857087/xxhash-3.8.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:83b9130b80b216d56fdf9e87131946b353c9627930c061955a101ea82b09fed9", size = 29879, upload-time = "2026-07-06T10:49:45.172Z" }, + { url = "https://files.pythonhosted.org/packages/08/0b/40a2a55ff52cf635bfdc5eae67a772bec85b4f44c6c737f73f6f528d51d1/xxhash-3.8.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8304be0982130954b7fd3aad18e2c6f8ee40254bc3d2e635991c16d77c91e2bd", size = 43246, upload-time = "2026-07-06T10:49:47.905Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/56ed2b6b200f26fb474f3fd387d95d0601efcd5bb33430c90c68924bdd77/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b512261801b1e5fde7b6ebf2fef7977339c620cbbca88a0040ad9ad134f4d02", size = 38202, upload-time = "2026-07-06T10:49:50.59Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/56864d895d1161a9f17502088e9c1fb7c06bde2c2efdde620d22bb7a9c43/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49aa8692507835dcc1e8ad8021f20c74c2dc13d83b5112e87877faa2a0035b20", size = 34448, upload-time = "2026-07-06T10:49:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/6b/57/5c6e0908a47f61dca96d01c8ee6fce01ed1050611eb779083ba8758fed81/xxhash-3.8.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:345b07b78e2bf583d71682aa34ae5b5fab575f7a1cb31e10263ebbc6f89f8c42", size = 32869, upload-time = "2026-07-06T10:49:55.972Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ac/cacdda1f0a90441297210bc34cf7e4ac1b7318c8030ebd83bdf6fe82f1db/yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750", size = 135466, upload-time = "2026-07-20T02:04:21.695Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a5/1b2ceace0230e40c52ab1b263148059a43a6303219b996affc68f8381836/yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2", size = 97291, upload-time = "2026-07-20T02:04:24.045Z" }, + { url = "https://files.pythonhosted.org/packages/59/1d/340d1a0db7bbce1f291afc044255ebf4ebbce2b25ab1b3f7d3d069080f5d/yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871", size = 97154, upload-time = "2026-07-20T02:04:25.761Z" }, + { url = "https://files.pythonhosted.org/packages/05/41/25596a33c2fb5098dca8dc3773b04221db64ded0b7f8f09885647d864610/yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0", size = 109196, upload-time = "2026-07-20T02:04:27.543Z" }, + { url = "https://files.pythonhosted.org/packages/f2/df/dd9f2fb8a5c6054fbefd1538d2b9b1127e612d2ee64b307a070173b57afd/yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e", size = 102556, upload-time = "2026-07-20T02:04:29.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/57/4754b9d2c8945880290ecba0864e8b0441e117bba70534fe819e3645e174/yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2", size = 117965, upload-time = "2026-07-20T02:04:30.845Z" }, + { url = "https://files.pythonhosted.org/packages/74/b5/6a9ece27d2043c3386f902dd078ab35d29ef5126b3206ebffb673283a7cb/yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621", size = 116266, upload-time = "2026-07-20T02:04:32.573Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bc/a6653249f6ee59ec85dcfec008d9cbc16586dad613963bb17a91b2b993a5/yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba", size = 110758, upload-time = "2026-07-20T02:04:34.235Z" }, + { url = "https://files.pythonhosted.org/packages/65/7e/c5a12fb8208df7b981bc82256e7831ce428eeaf893f7bbe6179c57bb9252/yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950", size = 110120, upload-time = "2026-07-20T02:04:35.85Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/1b659b964626694667b3ec01bf4bcff564b73ae7c48ea1fbfe588b78b461/yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00", size = 108834, upload-time = "2026-07-20T02:04:37.67Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/bf48f55c2104e40c15b7b13fad0a5756a11552a55f01c90bc90a66ab81c3/yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed", size = 103442, upload-time = "2026-07-20T02:04:39.576Z" }, + { url = "https://files.pythonhosted.org/packages/37/ac/84b273ac133ecdce598fc1f4140a08a1bf2044048bff8106371d207d105f/yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440", size = 117413, upload-time = "2026-07-20T02:04:41.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/55/9307e03977d3b290dfa42e5d2bae7b6140808fd1786fbe70cd9d3bee53c5/yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1", size = 109498, upload-time = "2026-07-20T02:04:43.468Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/791a6f314cb4c989c19f8e3a10271f1e469c077143915e52474d80f26b4b/yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6", size = 116062, upload-time = "2026-07-20T02:04:45.319Z" }, + { url = "https://files.pythonhosted.org/packages/19/1a/ddd3807b86055010e2f99aa89b3c640effdb65696766c20597f696f48a1c/yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d", size = 110941, upload-time = "2026-07-20T02:04:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/6d/03/f34271bba042d2187508bf62aea20a14129efb5a1acfc6a2efe7544630b4/yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224", size = 97534, upload-time = "2026-07-20T02:04:48.774Z" }, + { url = "https://files.pythonhosted.org/packages/e4/02/ecc8dc31b9f355731e700f8402b8075d2ea1737dbc4baf4abf0f0fc64288/yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13", size = 93603, upload-time = "2026-07-20T02:04:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/3cb5df059756a45761cc3dee8fd25ec82b83a6585ea3542b969fda850f99/yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3", size = 135043, upload-time = "2026-07-20T02:04:52.39Z" }, + { url = "https://files.pythonhosted.org/packages/44/f8/767d6bd5a03db63bc467df2fb56d6fafeae9667d74aea92cd6af399f828b/yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a", size = 96942, upload-time = "2026-07-20T02:04:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/ce/97/10b939c44d7b28d1dbc389cfc7012306d1ea8dba01eaef44b39fffaee52a/yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840", size = 97046, upload-time = "2026-07-20T02:04:56.638Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7a/b410dbe39b6255c55fb2a2bcee96eb844d0789235ddc381a889a90dc72d6/yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966", size = 110512, upload-time = "2026-07-20T02:04:58.955Z" }, + { url = "https://files.pythonhosted.org/packages/83/c7/da591971f78a5617e1f21f5699858ebccd836fe181a6493788ffc91ba69b/yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723", size = 102454, upload-time = "2026-07-20T02:05:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8e/73b0ed4de47289a78a96045d76d1cfe5e41848bf0da59ce25b2ec87ee05d/yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb", size = 117617, upload-time = "2026-07-20T02:05:02.325Z" }, + { url = "https://files.pythonhosted.org/packages/cf/14/b744747bc4f57a8d55bd744df463457524583e1e9f7538b5ace0346ab92e/yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780", size = 116135, upload-time = "2026-07-20T02:05:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/66/ca/95aa4d0e5b7ea4f20e4d577c42d001ed9df207569fdb063cc5ed4ebb496b/yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e", size = 111935, upload-time = "2026-07-20T02:05:05.738Z" }, + { url = "https://files.pythonhosted.org/packages/72/0d/d2ad8d6b147832d177a4e720ba1962fe686eb0913b74503b3eca094b8bba/yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2", size = 110010, upload-time = "2026-07-20T02:05:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/50/18/eb335e4120903903f4865041355ae46256a2406eb2865bc24827f4f27b61/yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58", size = 110058, upload-time = "2026-07-20T02:05:09.246Z" }, + { url = "https://files.pythonhosted.org/packages/44/70/97353add32c62ad6f206d948ac5a5ee84398225e534dc6ed6433d1b335b6/yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61", size = 103308, upload-time = "2026-07-20T02:05:11.31Z" }, + { url = "https://files.pythonhosted.org/packages/68/39/5e7398d4b6f6b3c9062823ebc60802df5b272e3fe9e788f9734c6ee46c85/yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6", size = 116898, upload-time = "2026-07-20T02:05:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/09e52f2239e8b96357eccca05915382e4ba5405ebfb623b6036040d99654/yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f", size = 109400, upload-time = "2026-07-20T02:05:14.821Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6a/e94133d4c2d1a14d2384310bf3e79d9cf32c9d1eae1c6f034fb80d098fa1/yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077", size = 115934, upload-time = "2026-07-20T02:05:17.78Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/34955ed967b976fc38edcbb6d538dee79dbda4cb7fc7f72a0907a7c78e0f/yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd", size = 112178, upload-time = "2026-07-20T02:05:19.675Z" }, + { url = "https://files.pythonhosted.org/packages/f5/46/d7bd3a8859d47dcfaffd7127af7076032a7da278a9a02e17b5f37bfb6712/yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25", size = 97544, upload-time = "2026-07-20T02:05:21.523Z" }, + { url = "https://files.pythonhosted.org/packages/01/69/c1bfd21e32c638974ea2c542a0b8c53ef1fa9eff336020f5d014f9503ff2/yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a", size = 93359, upload-time = "2026-07-20T02:05:23.493Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/71d051c850b5af41d168c679d9eb67eb7c55283ac4ee131673edf134bc4e/yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d", size = 136035, upload-time = "2026-07-20T02:05:25.489Z" }, + { url = "https://files.pythonhosted.org/packages/03/4d/8ad27f9a1b7e69313cca5d695b925b48efe51208d3490e0844bae97cabc0/yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec", size = 97642, upload-time = "2026-07-20T02:05:27.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/05b4131c407006cd1e410e9c6539f16a0945724677e5364447313c15ea3e/yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c", size = 97323, upload-time = "2026-07-20T02:05:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/20/16/e618c875c73e0e39611f20a581b3d5e8d59b8857bf001bee3263044c6deb/yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54", size = 107741, upload-time = "2026-07-20T02:05:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c4defeaf3ed33fcb346aacf9c6e971a8d4e2bde04a0310e79abb208e7965/yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12", size = 103570, upload-time = "2026-07-20T02:05:33.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e7/0e0e0de5865ebd5914537ef486f36c727a59865c3ac0cf5ff1b32aececbf/yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d", size = 115815, upload-time = "2026-07-20T02:05:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/2b/27/ca56b700cb170aba25a3893b75355b213935657dc5714d2383354a270e62/yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1", size = 116025, upload-time = "2026-07-20T02:05:37.503Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d0/d56c859b8222116f5d68459199f48359e0bf121b6f65a69bf329b3602ba0/yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9", size = 109835, upload-time = "2026-07-20T02:05:39.506Z" }, + { url = "https://files.pythonhosted.org/packages/70/a2/3a35557e4d1a79425040eba202ccaf08bdc8717680fc77e2498a1ad2e0a5/yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027", size = 108884, upload-time = "2026-07-20T02:05:41.584Z" }, + { url = "https://files.pythonhosted.org/packages/e4/35/ef4c26356b7913c68983bac2d72a4212b3347af551cb8d250b99b5ed7b7f/yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b", size = 107308, upload-time = "2026-07-20T02:05:43.697Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/ff0dc66c2ccf3e0153ab97ff61eabab4400e6a5264af427ab30cd69f1857/yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293", size = 103646, upload-time = "2026-07-20T02:05:45.895Z" }, + { url = "https://files.pythonhosted.org/packages/74/f0/33b9271c7f881766359d58266fa0811d2e5210ed860e28da7dc6d7786344/yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e", size = 115305, upload-time = "2026-07-20T02:05:47.832Z" }, + { url = "https://files.pythonhosted.org/packages/ef/65/fd79fb1868c4a80db8661091de525bf430f63c3bea1b20e8b6a84fc7d359/yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b", size = 108404, upload-time = "2026-07-20T02:05:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ba/dbabe6b262f17a816c70cfc09558dbf03ece3ec76684d02f911a3d3a189c/yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce", size = 115940, upload-time = "2026-07-20T02:05:51.741Z" }, + { url = "https://files.pythonhosted.org/packages/a5/43/fab2d1dad9d340a268cdde63756a123d069723efff6a372d123fa74a9517/yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba", size = 110006, upload-time = "2026-07-20T02:05:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/c4/27/41eb51bbd1b8d89546b83897cfb0164f1e109304fd408dbb151b639eec0f/yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b", size = 97618, upload-time = "2026-07-20T02:05:55.57Z" }, + { url = "https://files.pythonhosted.org/packages/3c/25/b2553764b3d65db711d8f45416351ec4f420847558eb669edcbcaadf5780/yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c", size = 93018, upload-time = "2026-07-20T02:05:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" }, + { url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" }, + { url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/5f3085fef9564217074db9dd8573de1795bc82cde61a7ad10b6a7234a569/yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76", size = 135680, upload-time = "2026-07-20T02:06:33.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/35/ba9436e579bd48a8801f2021d842d9ab4994c26e4c7dd3a4c1f1bcb57a9e/yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d", size = 97395, upload-time = "2026-07-20T02:06:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/a07f76f3c44e02b25cc743af5ef93eef27f7013eadca770451b6a6ccb5db/yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75", size = 97223, upload-time = "2026-07-20T02:06:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/77/f7/a9a1d6fa7dd9e388f95b30f6ad3ec4e285f6c8f61f44ce16070c3fcfe414/yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9", size = 108777, upload-time = "2026-07-20T02:06:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/2f/44/e0b86c302471fabd6f02808ecf2ac52b8412b624787849d4bf2cdb466f6f/yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede", size = 103119, upload-time = "2026-07-20T02:06:41.456Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/9c16d180bf8faaf223225eb50e1245870ff1ae0e302a27153988e65c51fd/yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca", size = 116471, upload-time = "2026-07-20T02:06:43.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8d/b219b9df28a02ce95cfbdd41d2f7caa5669d0ff979c1c9975697145e33c5/yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027", size = 115974, upload-time = "2026-07-20T02:06:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/f20557aca240d88e69850ad1ee91756821d094bb1310565c04d25c6682a2/yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9", size = 110830, upload-time = "2026-07-20T02:06:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/199b85109a53eeca64ee19c9cca228287e8e4ab0cc1a09b28f530e65cce0/yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41", size = 110054, upload-time = "2026-07-20T02:06:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/ed28147f8cd7f48c49367c90713b30a555284b6105a6a56f3a05568da795/yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373", size = 108312, upload-time = "2026-07-20T02:06:51.835Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/55e16ae0a5c227cea8df1c6871ba57d614a34243146c05729caf2a1bd9c5/yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36", size = 103662, upload-time = "2026-07-20T02:06:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ea/dbd7c2caec459c9a426f18b02688ecbfb58620d0f6a3422d24769fbaf8ab/yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0", size = 116090, upload-time = "2026-07-20T02:06:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/06/84/39ce4ce3059e07fece5fbdbee8c4053406af9aca911ce9fa5f8548aab6af/yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5", size = 109523, upload-time = "2026-07-20T02:06:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/71ff44137b405c64a7788075669c24010019f57a7464b78c3a6cbee539d9/yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5", size = 116084, upload-time = "2026-07-20T02:06:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/423078fdd4042e1862c11f0ffd977a0ffa393783c12bee94685923bc189e/yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4", size = 111006, upload-time = "2026-07-20T02:07:01.907Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/6daa2ee9d95e5c98b8128f8df91eb692eb423ab274b8cf08db52152fad26/yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad", size = 99215, upload-time = "2026-07-20T02:07:03.852Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0e/464a847d7359e0da75dd9fc5c1d1aa35d0159ea31e5f8e66a3c1c29ff3d0/yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f", size = 94566, upload-time = "2026-07-20T02:07:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/e03acc4446772660bc335e86e41ef31e4d0d838fd641531a11a5ee33b493/yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88", size = 142533, upload-time = "2026-07-20T02:07:08.284Z" }, + { url = "https://files.pythonhosted.org/packages/ae/71/4acd3a1fc7cf14345cdb302665ecd2097f62c365b4f14ca17d4f37775cf9/yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba", size = 100776, upload-time = "2026-07-20T02:07:10.197Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/cfb76b7fe99686db264bff829779a539d923e7564ffd7ef18da6c54c3774/yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928", size = 100913, upload-time = "2026-07-20T02:07:12.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3f/7116e782992abbd4fb6948488aec72078895e929a23078290739e8396fce/yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f", size = 106507, upload-time = "2026-07-20T02:07:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/d4d2d73ee78229cc889872eb8e085d8f5c6f51abdb178409fd9b23cf74fd/yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95", size = 99219, upload-time = "2026-07-20T02:07:16.019Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fa/a6df1a9bccd644eec00abee0dff4277416222cec435330fd1f2858523ec1/yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc", size = 111804, upload-time = "2026-07-20T02:07:18.141Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/7b2a1f4bcc20e9447156dd2b1c4d01f70d9df0759025ee7d09a84ffae134/yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da", size = 110943, upload-time = "2026-07-20T02:07:20.06Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/22c92affb0f9b623ca753d27d968b5625b868f12c6378d049d55ae247643/yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a", size = 108251, upload-time = "2026-07-20T02:07:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/45/44/5769b96298c1e195fb412997b6090af2a84105cf59c17613558a2d011d1f/yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0", size = 106025, upload-time = "2026-07-20T02:07:24.083Z" }, + { url = "https://files.pythonhosted.org/packages/4c/40/009e8e791fd9762c0e1567e69248acb4f49064597e1680874c16dd8bb798/yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498", size = 106573, upload-time = "2026-07-20T02:07:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/20/c6/b7480578f8a0a80946f36ad6df547ecec704f9ba69d2de60f8aa6f1c1cbf/yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104", size = 100751, upload-time = "2026-07-20T02:07:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/d4/27/4476f3360b91a48c5cf125e91f59a3bd35299d84a431a258d57f5977bb11/yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331", size = 111643, upload-time = "2026-07-20T02:07:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/5cdd3e5ee944e8af31e52f6cd3d3af5fd7b937e036ccbbba2c9ffebede95/yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550", size = 106312, upload-time = "2026-07-20T02:07:33.06Z" }, + { url = "https://files.pythonhosted.org/packages/18/86/f406b0c2a6f99575de2da671ef47aa06f89a5be83a27a46971c3b86cecdb/yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6", size = 110379, upload-time = "2026-07-20T02:07:35.155Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/9f3adfbd3b30b4fa0f7ccb3a83eba2c1152d3fff554d535e640ba0f7ba2b/yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047", size = 108497, upload-time = "2026-07-20T02:07:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/dd/37/91eb2e5ca883a529c1b390348a74cd9fc0512171727f547ce70bfe02be5c/yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104", size = 102450, upload-time = "2026-07-20T02:07:39.578Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" }, + { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +]