diff --git a/.containerignore b/.containerignore index 69ee40c..0931023 100644 --- a/.containerignore +++ b/.containerignore @@ -1,5 +1,6 @@ .git build +build-* benchmarks/data benchmarks/results __pycache__ diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index fe6feb6..0000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,136 +0,0 @@ -name: Benchmark - -on: - push: - branches: [main] - paths: - - 'lib/**' - - 'python-kalign/**' - - 'benchmarks/**' - - 'CMakeLists.txt' - - '.github/workflows/benchmark.yml' - pull_request: - branches: [main] - paths: - - 'lib/**' - - 'python-kalign/**' - - 'benchmarks/**' - - 'CMakeLists.txt' - - '.github/workflows/benchmark.yml' - workflow_dispatch: - -permissions: - contents: write - deployments: write - -jobs: - benchmark: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y libomp-dev cmake - - - name: Build C binary - run: | - mkdir -p build - cd build - cmake .. - make -j$(nproc) - - - name: Install Python package - run: | - python -m pip install --upgrade pip - python -m pip install -e . - - - name: Cache BAliBASE dataset - uses: actions/cache@v4 - with: - path: benchmarks/data/downloads - key: benchmark-datasets-balibase-v1 - - - name: Run benchmarks - run: | - python -m benchmarks \ - --dataset balibase \ - --method python_api cli \ - --binary build/src/kalign \ - --output benchmarks/results/latest.json \ - -v - - - name: Check if results were produced - id: check_results - run: | - if [ -f benchmarks/results/latest.json ]; then - echo "has_results=true" >> "$GITHUB_OUTPUT" - else - echo "::warning::No benchmark results produced (dataset download may have failed)" - echo "has_results=false" >> "$GITHUB_OUTPUT" - fi - - - name: Convert results for github-action-benchmark - if: steps.check_results.outputs.has_results == 'true' - run: | - python -c " - import json - with open('benchmarks/results/latest.json') as f: - data = json.load(f) - entries = [] - for method, stats in data.get('summary', {}).items(): - entries.append({ - 'name': f'SP Score Mean ({method})', - 'unit': 'score', - 'value': round(stats['sp_mean'], 2), - 'range': f\"{stats['sp_min']:.1f}-{stats['sp_max']:.1f}\", - }) - entries.append({ - 'name': f'Total Time ({method})', - 'unit': 'seconds', - 'value': round(stats['total_time'], 2), - }) - with open('benchmarks/results/benchmark_output.json', 'w') as f: - json.dump(entries, f, indent=2) - " - - - name: Store benchmark result - if: github.ref == 'refs/heads/main' && steps.check_results.outputs.has_results == 'true' - uses: benchmark-action/github-action-benchmark@v1 - with: - tool: 'customBiggerIsBetter' - output-file-path: benchmarks/results/benchmark_output.json - github-token: ${{ secrets.GITHUB_TOKEN }} - gh-pages-branch: gh-pages - benchmark-data-dir-path: dev/bench - auto-push: true - alert-threshold: '95%' - comment-on-alert: true - fail-on-alert: false - - - name: Compare with baseline (PRs only) - if: github.event_name == 'pull_request' && steps.check_results.outputs.has_results == 'true' - uses: benchmark-action/github-action-benchmark@v1 - with: - tool: 'customBiggerIsBetter' - output-file-path: benchmarks/results/benchmark_output.json - github-token: ${{ secrets.GITHUB_TOKEN }} - gh-pages-branch: gh-pages - benchmark-data-dir-path: dev/bench - auto-push: false - alert-threshold: '95%' - comment-on-alert: true - fail-on-alert: true - - - name: Upload results artifact - if: steps.check_results.outputs.has_results == 'true' - uses: actions/upload-artifact@v4 - with: - name: benchmark-results - path: benchmarks/results/ diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d746956..c9f819a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -35,12 +35,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libomp-dev cmake + sudo apt-get install -y cmake - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install --formula libomp cmake + brew install --formula cmake - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} @@ -83,7 +83,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libomp-dev cmake + sudo apt-get install -y cmake - name: Configure CMake with ASAN run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=ASAN diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 1be1b89..be919ff 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -68,12 +68,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libomp-dev cmake + sudo apt-get install -y cmake - name: Install system dependencies (macOS) if: runner.os == 'macOS' run: | - brew install libomp cmake + brew install cmake - name: Install Python dependencies run: | @@ -116,12 +116,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libomp-dev cmake + sudo apt-get install -y cmake - name: Install system dependencies (macOS) if: runner.os == 'macOS' run: | - brew install libomp cmake + brew install cmake - name: Install build dependencies run: | diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index bd5b8e9..4f5a79a 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -59,10 +59,12 @@ jobs: CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* cp313-* CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - # Set minimum macOS version to match OpenMP requirements + # Disable OpenMP on macOS to avoid libomp.dylib conflicts + # Use built-in threadpool for parallelization instead + CIBW_CONFIG_SETTINGS_MACOS: > + cmake.args=-DUSE_OPENMP=OFF;-DUSE_THREADPOOL=ON CIBW_ENVIRONMENT_MACOS: > CMAKE_BUILD_PARALLEL_LEVEL=2 - OMP_NUM_THREADS=1 MACOSX_DEPLOYMENT_TARGET=14.0 CMAKE_OSX_DEPLOYMENT_TARGET=14.0 @@ -75,15 +77,14 @@ jobs: # macOS specific settings CIBW_BEFORE_ALL_MACOS: | - brew install --formula cmake libomp || echo "Dependencies may already be installed" + brew install --formula cmake || echo "cmake already installed" CIBW_REPAIR_WHEEL_COMMAND_MACOS: > delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} # Environment variables for builds CIBW_ENVIRONMENT: > CMAKE_BUILD_PARALLEL_LEVEL=2 - OMP_NUM_THREADS=1 - + # Skip testing during wheel build to avoid cross-compilation issues CIBW_TEST_SKIP: "*" diff --git a/.gitignore b/.gitignore index d0d6326..c1f7678 100644 --- a/.gitignore +++ b/.gitignore @@ -419,3 +419,7 @@ callgrind.out.* *.large.fasta test_data/large/ +# Benchmark data (downloaded datasets + simulation results) +benchmarks/data/ +benchmarks/results/ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a7d3f0..b5653bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ include(GenerateExportHeader) set(KALIGN_LIBRARY_VERSION_MAJOR 3) set(KALIGN_LIBRARY_VERSION_MINOR 5) -set(KALIGN_LIBRARY_VERSION_PATCH 1) +set(KALIGN_LIBRARY_VERSION_PATCH 2) set(KALIGN_LIBRARY_VERSION_STRING ${KALIGN_LIBRARY_VERSION_MAJOR}.${KALIGN_LIBRARY_VERSION_MINOR}.${KALIGN_LIBRARY_VERSION_PATCH}) @@ -61,14 +61,18 @@ include(GNUInstallDirs) include(CTest) include(CheckCSourceRuns) -option(USE_OPENMP "Use OpenMP for parallelization" ON) +option(USE_OPENMP "Use OpenMP for parallelization" OFF) +option(USE_THREADPOOL "Use built-in threadpool instead of OpenMP" ON) option(ENABLE_SSE "Enable compile-time SSE4.1 support." ON) option(ENABLE_AVX "Enable compile-time AVX support." ON) option(ENABLE_AVX2 "Enable compile-time AVX2 support." ON) # Performance tuning parameters -set(KALIGN_ALN_SERIAL_THRESHOLD "250" CACHE STRING "Alignment positions threshold below which to use serial instead of parallel processing") -set(KALIGN_KMEANS_UPGMA_THRESHOLD "50" CACHE STRING "Number of sequences threshold below which to use UPGMA instead of parallel k-means") +# Threadpool parallelization thresholds (all settings in one place) +set(KALIGN_ALN_SERIAL_THRESHOLD "500" CACHE STRING "Hirschberg DP width below which to run serial") +set(KALIGN_KMEANS_UPGMA_THRESHOLD "50" CACHE STRING "Sequence count below which to use UPGMA instead of parallel k-means") +set(KALIGN_DIST_MIN_SEQS "50" CACHE STRING "Minimum sequences to parallelize distance/anchor computations") +set(KALIGN_PFOR_MIN_CHUNK "10" CACHE STRING "Minimum iterations per chunk in tp_parallel_for") # option(ENABLE_FMA "Enable compile-time FMA support." ON) # option(ENABLE_AVX512 "Enable compile-time AVX512 support." ON) @@ -96,6 +100,20 @@ if(USE_OPENMP) endif(OPENMP_FOUND OR OpenMP_FOUND) endif(USE_OPENMP) +# Built-in threadpool (alternative to OpenMP) +if(USE_THREADPOOL) + if(USE_OPENMP AND (OPENMP_FOUND OR OpenMP_FOUND)) + message(WARNING "Both USE_OPENMP and USE_THREADPOOL are ON. Threadpool takes precedence.") + endif() + find_package(Threads QUIET) + if(Threads_FOUND) + add_definitions(-DUSE_THREADPOOL) + message(STATUS "Built-in threadpool is enabled.") + else() + set(USE_THREADPOOL OFF) + message(STATUS "pthreads not found — falling back to serial execution.") + endif() +endif() if (ENABLE_SSE) # @@ -199,6 +217,8 @@ endif(HAVE_AVX2) # Add performance tuning parameters as compile definitions add_definitions(-DKALIGN_ALN_SERIAL_THRESHOLD=${KALIGN_ALN_SERIAL_THRESHOLD}) add_definitions(-DKALIGN_KMEANS_UPGMA_THRESHOLD=${KALIGN_KMEANS_UPGMA_THRESHOLD}) +add_definitions(-DKALIGN_DIST_MIN_SEQS=${KALIGN_DIST_MIN_SEQS}) +add_definitions(-DKALIGN_PFOR_MIN_CHUNK=${KALIGN_PFOR_MIN_CHUNK}) add_subdirectory(lib) add_subdirectory(src) @@ -223,8 +243,10 @@ if(BUILD_PYTHON_MODULE) # Link against the static kalign library target_link_libraries(_core PRIVATE kalign_static) - # Link OpenMP if found - if(OpenMP_CXX_FOUND) + # Link OpenMP or threadpool + if(USE_THREADPOOL) + target_link_libraries(_core PRIVATE Threads::Threads) + elseif(OpenMP_CXX_FOUND) target_link_libraries(_core PRIVATE OpenMP::OpenMP_CXX) endif() diff --git a/ChangeLog b/ChangeLog index 1388445..f2a5611 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,57 @@ -2026-02-27 Timo Lassmann +2026-05-16 Timo Lassmann + + * version 3.5.2 - Four-mode preset system, threadpool, hardening + Algorithm and presets + - Four mode presets stable for protein and nucleotide: + fast, default, recall, accurate. Per-mode configurations + derived by NSGA-III multi-objective optimisation on + BAliBASE v4 (protein) and BRAliBASE (RNA). + - Unified nucleotide preset (DNA and RNA share one path). + - Ensemble alignment with POAR consensus and per-column / + per-residue confidence scores. + - New --add mode: append sequences to an existing alignment. + - Sparse consistency-bonus matrix for large MSAs (avoids + quadratic memory in column count). + - Removed deprecated 'precise' mode alias (Python only; + never shipped as a wheel). + + Parallelism + - Chase-Lev work-stealing thread pool is now the default + parallelism backend; OpenMP is optional (USE_OPENMP=ON). + - macOS wheels no longer link libomp.dylib, resolving the + conda-forge / numpy OpenMP runtime conflict. + + Security / robustness + - POAR file parsing now rejects malformed inputs that would + cause integer overflow in pair-count or per-pair entry + count (lib/src/poar.c). Limited threat model (requires + explicit --load-poar) but tightened anyway. + - finalise_alignment is now atomic w.r.t. msa->sequences: + validates all per-sequence linear lengths before swapping + any pointer, so a failure leaves the MSA unchanged. + - kalign_msa_compare wraps finalise_alignment in RUN(); a + failure surfaces cleanly instead of producing a half- + finalised MSA. + - DSSIM stress test now uses KALIGN_MATRIX_AUTO so it + exercises both protein and DNA biotypes correctly. + - Bumped locked dependencies (cryptography, flask, werkzeug, + pillow, pygments, pytest, urllib3, requests, mako, black) + past their CVE fix versions. + + Build / tooling + - build.zig updated for zig 0.16 (four cross-compile targets). + - tests/check-local.sh: one-command pre-push gate that runs + zig build + native ctest + Linux ASAN container + pytest. + - Containerfile.memcheck (Ubuntu + ASAN + Valgrind) for local + Linux memory-bug reproduction. + - Removed paper-side benchmark machinery, optimizer scripts, + and design-rationale PRDs from the public tree (preserved + in the manuscript repository for reproducibility). + - Cleaned up ~550 lines of dead code (coretralign, + bitShiftRight256ymm, unused split() in bisectingKmeans). + - Build is now warning-free on clang and GCC. + - Dropped the broken benchmark CI workflow (BAliBASE download + endpoint has been gone for months). * version 3.5.1 - Bugfix release - Fix memory leak in build_tree_from_pairwise (realign/ensemble) diff --git a/Containerfile b/Containerfile index 15fe4c7..d84de66 100644 --- a/Containerfile +++ b/Containerfile @@ -3,25 +3,23 @@ # Includes kalign, Clustal Omega, MAFFT, and MUSCLE v5 for comparative # benchmarking on BAliBASE, BRAliBASE, and BaliFam100 datasets. # -# Build: +# Build (installs kalign from the 'extra' branch): # podman build -t kalign-benchmark . # -# Run the interactive dashboard: -# podman run -it -p 8050:8050 \ -# -v ./benchmarks/data:/kalign/benchmarks/data \ -# kalign-benchmark +# Build from a specific commit for reproducibility: +# podman build --build-arg KALIGN_REF=abc1234 -t kalign-benchmark . # -# Run a CLI benchmark: +# Run benchmarks: # podman run -it \ -# -v ./benchmarks/data:/kalign/benchmarks/data \ +# -v ./benchmarks/data:/data \ # kalign-benchmark \ # python -m benchmarks \ -# --dataset balibase --method python_api clustalo mafft muscle -v +# --dataset balibase --method cli clustalo mafft muscle \ +# --mode fast default recall accurate -v # -# View results in the dashboard after a CLI run: +# Run the interactive dashboard: # podman run -it -p 8050:8050 \ -# -v ./benchmarks/data:/kalign/benchmarks/data \ -# -v ./benchmarks/results:/kalign/benchmarks/results \ +# -v ./benchmarks/data:/data \ # kalign-benchmark FROM ubuntu:24.04 @@ -37,7 +35,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # ---------- Build MUSCLE v5 from source ---------- -# myutils.h checks __arm64__ (macOS) but not __aarch64__ (Linux); add it RUN cd /tmp && \ git clone --depth 1 https://github.com/rcedgar/muscle.git && \ cd muscle/src && \ @@ -46,33 +43,32 @@ RUN cd /tmp && \ cp ../bin/muscle /usr/local/bin/ && \ rm -rf /tmp/muscle -# ---------- Copy kalign source and build ---------- -COPY . /kalign -WORKDIR /kalign - -RUN mkdir -p build && cd build && \ - cmake -DCMAKE_BUILD_TYPE=Release .. && \ - make -j"$(nproc)" - # ---------- Python environment ---------- RUN python3 -m venv /venv -ENV PATH="/venv/bin:/kalign/build/src:$PATH" +ENV PATH="/venv/bin:$PATH" +RUN pip install --no-cache-dir uv -RUN pip install --no-cache-dir uv && \ - uv pip install --no-cache -e ".[benchmark]" +# ---------- Clone kalign and build ---------- +ARG KALIGN_REF=extra +RUN git clone --branch ${KALIGN_REF} --depth 1 \ + https://github.com/TimoLassmann/kalign.git /kalign +WORKDIR /kalign -# ---------- Verify tools ---------- -RUN which kalign && which clustalo && which mafft && which muscle +# Build kalign C binary (threadpool, no OpenMP) +RUN mkdir cbuild && cd cbuild && \ + cmake -DCMAKE_BUILD_TYPE=Release -DUSE_OPENMP=OFF -DUSE_THREADPOOL=ON .. && \ + make -j"$(nproc)" && \ + cp src/kalign /usr/local/bin/kalign -# ---------- Data & results directories ---------- -RUN mkdir -p /kalign/benchmarks/data/downloads /kalign/benchmarks/results +# Install Python package with benchmark dependencies +RUN uv pip install --no-cache -e ".[benchmark]" \ + --config-settings='cmake.args=-DUSE_OPENMP=OFF;-DUSE_THREADPOOL=ON' -# ---------- Hot-swap: cross-compiled kalign binary (last for fast rebuilds) ---------- -COPY zig-out/kalign-linux-aarch64 /usr/local/bin/kalign -RUN chmod +x /usr/local/bin/kalign +# ---------- Verify all tools ---------- +RUN kalign --version && clustalo --version && mafft --version && muscle -version -# Rebuild Python module with latest source (uses cached venv layer) -RUN uv pip install --no-cache -e ".[benchmark]" +# ---------- Data directory (mount point for BAliBASE etc.) ---------- +RUN mkdir -p /kalign/benchmarks/data/downloads /kalign/benchmarks/results EXPOSE 8050 diff --git a/Containerfile.downstream b/Containerfile.downstream deleted file mode 100644 index e144ed8..0000000 --- a/Containerfile.downstream +++ /dev/null @@ -1,160 +0,0 @@ -# Kalign Downstream Benchmark Container -# -# Extends the base benchmark setup with tools for downstream application -# benchmarks: positive selection (HyPhy), phylogenetics (IQ-TREE), -# homology detection (HMMER), and confidence comparison (GUIDANCE2). -# -# Build: -# podman build -f Containerfile.downstream -t kalign-downstream . -# -# Run all downstream benchmarks: -# podman run -it \ -# -v ./benchmarks/data:/kalign/benchmarks/data \ -# -v ./benchmarks/results:/kalign/benchmarks/results \ -# kalign-downstream \ -# python -m benchmarks.downstream --all -j 4 -# -# Quick smoke test (5 cases per pipeline): -# podman run -it \ -# -v ./benchmarks/data:/kalign/benchmarks/data \ -# -v ./benchmarks/results:/kalign/benchmarks/results \ -# kalign-downstream \ -# python -m benchmarks.downstream --all -j 4 --quick -# -# Generate figures from existing results: -# podman run -it \ -# -v ./benchmarks/results:/kalign/benchmarks/results \ -# -v ./benchmarks/figures:/kalign/benchmarks/figures \ -# kalign-downstream \ -# python -m benchmarks.downstream --figures -o benchmarks/figures/ - -FROM ubuntu:24.04 - -ENV DEBIAN_FRONTEND=noninteractive - -# ── System dependencies ────────────────────────────────────────────── -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential cmake g++ git curl wget ca-certificates \ - python3 python3-pip python3-venv python3-dev \ - pkg-config zlib1g-dev libcurl4-openssl-dev libssl-dev libeigen3-dev libboost-dev \ - clustalo mafft hmmer \ - perl libwww-perl libbio-perl-perl cpanminus \ - && rm -rf /var/lib/apt/lists/* - -# Bio::Perl convenience module (removed from BioPerl core in 1.7.x) -RUN cpanm --notest Bio::Perl - -# ── MUSCLE v5 from source ─────────────────────────────────────────── -# myutils.h checks __arm64__ (macOS) but not __aarch64__ (Linux); add it -RUN cd /tmp && \ - git clone --depth 1 https://github.com/rcedgar/muscle.git && \ - cd muscle/src && \ - sed -i 's/defined(__arm64__)/defined(__arm64__) || defined(__aarch64__)/' myutils.h && \ - bash build_linux.bash && \ - cp ../bin/muscle /usr/local/bin/ && \ - rm -rf /tmp/muscle - -# ── INDELible v1.03 from source ───────────────────────────────────── -RUN cd /tmp && \ - git clone --depth 1 https://github.com/matsengrp/indelible.git && \ - cd indelible/src && \ - make && \ - cp indelible /usr/local/bin/ && \ - rm -rf /tmp/indelible - -# ── HyPhy from source ─────────────────────────────────────────────── -RUN cd /tmp && \ - git clone --depth 1 https://github.com/veg/hyphy.git && \ - cd hyphy && \ - cmake -DCMAKE_BUILD_TYPE=Release -DNOAVX=ON . && \ - make -j"$(nproc)" hyphy && \ - cp hyphy /usr/local/bin/hyphy && \ - cp -r res /usr/local/lib/hyphy && \ - rm -rf /tmp/hyphy -ENV HYPHY_LIB=/usr/local/lib/hyphy -ENV HYPHY_PATH=/usr/local/lib/hyphy - -# ── IQ-TREE 2 from source ─────────────────────────────────────────── -RUN cd /tmp && \ - git clone --depth 1 --recurse-submodules https://github.com/iqtree/iqtree2.git && \ - cd iqtree2 && \ - mkdir build && cd build && \ - cmake -DCMAKE_BUILD_TYPE=Release .. && \ - make -j"$(nproc)" && \ - cp iqtree2 /usr/local/bin/ && \ - rm -rf /tmp/iqtree2 - -# ── GUIDANCE2 from GitHub (original tar.gz URL is dead) ──────────── -# guidance.pl uses FindBin-relative paths ($Bin, $Bin/../Selecton, etc.) -# We install the full www/ tree under /opt/guidance-root/ so sibling -# directories (Selecton, bioSequence_scripts_and_constants) resolve -# correctly relative to the guidance.pl script location. -RUN cd /tmp && \ - git clone --depth 1 https://github.com/anzaika/guidance.git && \ - cd guidance && make && \ - mkdir -p /opt/guidance-root && \ - cp -r www/Guidance /opt/guidance-root/Guidance && \ - cp -r www/Selecton /opt/guidance-root/Selecton && \ - cp -r www/bioSequence_scripts_and_constants /opt/guidance-root/bioSequence_scripts_and_constants && \ - mkdir -p /opt/guidance-root/Guidance/exec && \ - cp programs/msa_set_score/msa_set_score /opt/guidance-root/Guidance/exec/ && \ - cp programs/removeTaxa/removeTaxa /opt/guidance-root/Guidance/exec/ && \ - cp programs/isEqualTree/isEqualTree /opt/guidance-root/Guidance/exec/ && \ - chmod +x /opt/guidance-root/Guidance/guidance.pl && \ - mkdir -p /opt/programs/semphy /opt/programs/msa_set_score \ - /opt/programs/removeTaxa /opt/programs/isEqualTree && \ - cp programs/semphy/semphy /opt/programs/semphy/ && \ - cp programs/msa_set_score/msa_set_score /opt/programs/msa_set_score/ && \ - cp programs/removeTaxa/removeTaxa /opt/programs/removeTaxa/ && \ - cp programs/isEqualTree/isEqualTree /opt/programs/isEqualTree/ && \ - printf '#!/bin/sh\nexec perl /opt/guidance-root/Guidance/guidance.pl "$@"\n' \ - > /usr/local/bin/guidance2 && \ - chmod +x /usr/local/bin/guidance2 && \ - rm -rf /tmp/guidance - -# ── Kalign C build ────────────────────────────────────────────────── -COPY . /kalign -WORKDIR /kalign - -RUN mkdir -p build && cd build && \ - cmake -DCMAKE_BUILD_TYPE=Release .. && \ - make -j"$(nproc)" - -# ── Record tool versions at build time ────────────────────────────── -RUN echo "build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /tool_versions.txt && \ - echo "kalign=$(build/src/kalign --version 2>&1 | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "mafft=$(mafft --version 2>&1 | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "muscle=$(muscle --version 2>&1 | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "clustalo=$(clustalo --version 2>&1 | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "hmmer=$(hmmbuild -h 2>&1 | grep '^# HMMER' | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "iqtree=$(iqtree2 --version 2>&1 | grep 'IQ-TREE' | head -1 || echo unknown)" >> /tool_versions.txt && \ - echo "indelible=1.03" >> /tool_versions.txt && \ - echo "hyphy=$(hyphy --version 2>&1 | head -1 || echo unknown)" >> /tool_versions.txt - -# ── Python environment ────────────────────────────────────────────── -RUN python3 -m venv /venv -ENV PATH="/venv/bin:/kalign/build/src:$PATH" - -RUN pip install --no-cache-dir uv && \ - uv pip install --no-cache -e ".[benchmark]" && \ - uv pip install --no-cache \ - dendropy biopython pandas matplotlib scipy seaborn numpy - -# ── Verify tools ──────────────────────────────────────────────────── -RUN which kalign && which clustalo && which mafft && which muscle && \ - which hmmbuild && which hmmsearch && which iqtree2 && \ - which hyphy && which indelible && which guidance2 - -# ── Data & results directories ────────────────────────────────────── -RUN mkdir -p benchmarks/data/downloads/pfam_seed \ - benchmarks/data/downloads/swissprot \ - benchmarks/data/downloads/selectome \ - benchmarks/results/calibration \ - benchmarks/results/positive_selection \ - benchmarks/results/phylo_accuracy \ - benchmarks/results/hmmer_detection \ - benchmarks/figures - -EXPOSE 8050 - -CMD ["python", "-m", "benchmarks.downstream", "--help"] diff --git a/Containerfile.memcheck b/Containerfile.memcheck new file mode 100644 index 0000000..78832b8 --- /dev/null +++ b/Containerfile.memcheck @@ -0,0 +1,80 @@ +# Kalign Memory Debugging Container +# +# Linux-based container with ASAN (detect_leaks), Valgrind, and glibc +# for finding memory bugs that don't manifest on macOS. +# +# Build: +# podman build -f Containerfile.memcheck -t kalign-memcheck . +# +# Run ASAN stress test: +# podman run -it kalign-memcheck /kalign/run_memcheck.sh asan +# +# Run Valgrind stress test: +# podman run -it kalign-memcheck /kalign/run_memcheck.sh valgrind +# +# Run Python stress test with ASAN: +# podman run -it kalign-memcheck /kalign/run_memcheck.sh python +# +# Interactive shell: +# podman run -it kalign-memcheck bash + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# System dependencies + debugging tools +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake g++ git curl \ + python3 python3-pip python3-venv python3-dev \ + valgrind \ + pkg-config \ + libomp-dev \ + && rm -rf /var/lib/apt/lists/* + +# ---------- Copy kalign source ---------- +COPY . /kalign +WORKDIR /kalign + +# ---------- Build 1: ASAN build (for C tests + Python module) ---------- +RUN mkdir -p build-asan && cd build-asan && \ + cmake -DCMAKE_BUILD_TYPE=ASAN -DBUILD_PYTHON_MODULE=OFF .. && \ + make -j"$(nproc)" + +# ---------- Build 2: Debug build (for Valgrind — no ASAN) ---------- +RUN mkdir -p build-debug && cd build-debug && \ + cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_PYTHON_MODULE=OFF .. && \ + make -j"$(nproc)" + +# ---------- Build 3: Release build (for Python module) ---------- +RUN mkdir -p build-release && cd build-release && \ + cmake -DCMAKE_BUILD_TYPE=Release .. && \ + make -j"$(nproc)" + +# ---------- Python environment ---------- +RUN python3 -m venv /venv +ENV PATH="/venv/bin:$PATH" + +RUN pip install --no-cache-dir uv && \ + cd /kalign && uv pip install --no-cache -e . + +# ---------- Compile C stress test (ASAN) ---------- +RUN cc -fsanitize=address -O0 -g -DDEBUG \ + -I/kalign/lib/include -I/kalign/lib/src \ + /kalign/tests/memcheck_stress.c \ + -L/kalign/build-asan/lib -lkalign_static -ltldevel \ + -fopenmp -lm \ + -o /kalign/build-asan/memcheck_stress + +# ---------- Compile C stress test (Debug, for Valgrind) ---------- +RUN cc -O0 -g -DDEBUG \ + -I/kalign/lib/include -I/kalign/lib/src \ + /kalign/tests/memcheck_stress.c \ + -L/kalign/build-debug/lib -lkalign_static -ltldevel \ + -fopenmp -lm \ + -o /kalign/build-debug/memcheck_stress + +# ---------- Entry script ---------- +COPY tests/run_memcheck.sh /kalign/run_memcheck.sh +RUN chmod +x /kalign/run_memcheck.sh + +CMD ["/kalign/run_memcheck.sh", "all"] diff --git a/README-python.md b/README-python.md index 1f09c8c..78cf516 100644 --- a/README-python.md +++ b/README-python.md @@ -29,25 +29,28 @@ sequences = [ "ATCGATCATCG" ] -# Default mode — consistency anchors + VSM (best general-purpose) +# Default mode — single run with consistency anchors (best general-purpose) aligned = kalign.align(sequences) -# Fast mode — no consistency, fastest +# Fast mode — single run, fastest aligned = kalign.align(sequences, mode="fast") -# Precise mode — ensemble + realign, highest precision -aligned = kalign.align(sequences, mode="precise") +# Accurate mode — ensemble, highest precision +aligned = kalign.align(sequences, mode="accurate") ``` ## Modes -Kalign v3.5 provides three named modes that package the best configurations: +Kalign v3.5 provides four named modes, exposed identically through the Python API +and the `kalign` command-line tool. Presets were derived from NSGA-III +multi-objective optimization on BAliBASE v4 (protein) and BRAliBASE (RNA). | Mode | Python | CLI | Description | |------|--------|-----|-------------| -| **default** | `mode="default"` or omit | `kalign` | Consistency anchors + VSM. Best general-purpose. | -| **fast** | `mode="fast"` | `kalign --fast` | VSM only. Fastest, similar to kalign v3.4. | -| **precise** | `mode="precise"` | `kalign --precise` | Ensemble(3) + VSM + realign. Highest precision. | +| **fast** | `mode="fast"` | `kalign --mode fast` | Single run, fastest. | +| **default** | `mode="default"` or omit | `kalign` (or `--mode default`) | Single run with consistency anchors. Best general-purpose. | +| **recall** | `mode="recall"` | `kalign --mode recall` | Ensemble, optimized for recall. | +| **accurate** | `mode="accurate"` | `kalign --mode accurate` | Ensemble, highest precision. | Explicit parameters always override mode defaults: @@ -55,11 +58,12 @@ Explicit parameters always override mode defaults: # Fast base + 5 ensemble runs aligned = kalign.align(sequences, mode="fast", ensemble=5) -# Precise base + custom gap penalty -aligned = kalign.align(sequences, mode="precise", gap_open=8.0) +# Accurate base + custom gap penalty +aligned = kalign.align(sequences, mode="accurate", gap_open=8.0) ``` -Mode constants are also available: `kalign.MODE_DEFAULT`, `kalign.MODE_FAST`, `kalign.MODE_PRECISE`. +Mode constants are also available: `kalign.MODE_FAST`, `kalign.MODE_DEFAULT`, +`kalign.MODE_RECALL`, `kalign.MODE_ACCURATE`. ## Core API @@ -68,7 +72,7 @@ Mode constants are also available: `kalign.MODE_DEFAULT`, `kalign.MODE_FAST`, `k ```python aligned = kalign.align( sequences, # list of str - mode=None, # "default", "fast", "precise", or None (= default) + mode=None, # "fast", "default", "recall", "accurate", or None (= default) seq_type="auto", # "auto", "dna", "rna", "protein", "divergent", "internal" gap_open=None, # positive float, or None for defaults gap_extend=None, # positive float, or None for defaults @@ -103,8 +107,8 @@ Additional parameters for advanced use: ```python result = kalign.align_from_file( "input.fasta", - mode="precise", # or "default", "fast" - ensemble=5, # override: 5 runs instead of mode default (3) + mode="accurate", # or "fast", "default", "recall" + ensemble=5, # override: 5 runs instead of mode default min_support=0, # consensus threshold (0 = auto) save_poar="consensus.poar", # save POAR table for re-thresholding # load_poar="consensus.poar", # OR load pre-computed POAR @@ -142,13 +146,13 @@ Supported formats: `fasta`, `clustal`, `stockholm`, `phylip` (non-FASTA formats ## Ensemble Alignment & Confidence Scores -Ensemble mode runs multiple alignments with varied parameters and combines results via POAR (Pairs of Aligned Residues) consensus. The simplest way to use it is `mode="precise"` (ensemble=3 + realign). For more control, set `ensemble` directly. +Ensemble mode runs multiple alignments with varied parameters and combines results via POAR (Pairs of Aligned Residues) consensus. The simplest way to use it is `mode="accurate"` (5-run ensemble) or `mode="recall"` (recall-tuned ensemble). For more control, set `ensemble` directly. ```python import kalign -# Precise mode: ensemble(3) + realign — highest precision -result = kalign.align_from_file("proteins.fasta", mode="precise") +# Accurate mode: 5-run ensemble — highest precision +result = kalign.align_from_file("proteins.fasta", mode="accurate") # Or: explicit 5 ensemble runs result = kalign.align_from_file("proteins.fasta", ensemble=5) @@ -298,8 +302,9 @@ print(type(aln)) # ```bash # Modes kalign-py -i sequences.fasta -o aligned.fasta # default mode -kalign-py --fast -i sequences.fasta -o aligned.fasta # fast mode -kalign-py --precise -i sequences.fasta -o aligned.fasta # precise mode +kalign-py --mode fast -i sequences.fasta -o aligned.fasta # fast mode +kalign-py --mode recall -i sequences.fasta -o aligned.fasta # recall mode +kalign-py --mode accurate -i sequences.fasta -o aligned.fasta # accurate mode # I/O options kalign-py -i sequences.fasta -o - --format clustal # stdout diff --git a/README.md b/README.md index db40270..7bf7fd5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ alignment approach with multi-threading support. ### From source -Prerequisites: C compiler (GCC or Clang), CMake 3.18+, optionally OpenMP. +Prerequisites: C compiler (GCC or Clang), CMake 3.18+. ```bash mkdir build && cd build @@ -23,7 +23,11 @@ make test make install ``` -On macOS, `brew install libomp` for OpenMP support. +Kalign uses a built-in thread pool for parallelization (requires pthreads, available on all POSIX systems). If pthreads is not available, it falls back to serial execution. To use OpenMP instead: + +```bash +cmake -DUSE_OPENMP=ON -DUSE_THREADPOOL=OFF .. +``` ### Zig build (alternative) @@ -47,47 +51,43 @@ See [README-python.md](README-python.md) for the full Python documentation. kalign -i -o ``` -Kalign v3.5 has three modes: +Kalign has four mode presets, optimized for protein and nucleotide sequences: | Mode | Flag | Description | |------|------|-------------| -| default | (none) | Best general-purpose. | -| fast | `--fast` | Fastest. Same as kalign v3.4. | -| precise | `--precise` | Highest accuracy, ~10x slower. | +| fast | `--mode fast` | Single run, fastest. | +| default | `--mode default` | Single run with consistency anchors (default). | +| recall | `--mode recall` | Ensemble, optimized for recall. | +| accurate | `--mode accurate` | Ensemble, highest precision. | ### Examples ```bash -# Align sequences +# Align sequences (default mode) kalign -i sequences.fa -o aligned.fa # Fast mode -kalign --fast -i sequences.fa -o aligned.fa +kalign --mode fast -i sequences.fa -o aligned.fa -# Precise mode (ensemble + realign) -kalign --precise -i sequences.fa -o aligned.fa +# Accurate mode (ensemble) +kalign --mode accurate -i sequences.fa -o aligned.fa # Read from stdin cat input.fa | kalign -i - -o aligned.fa # Combine multiple input files kalign seqsA.fa seqsB.fa -o combined.fa - -# Save ensemble consensus for re-thresholding -kalign --precise -i seqs.fa -o out.fa --save-poar consensus.poar -kalign -i seqs.fa -o out2.fa --load-poar consensus.poar --min-support 3 ``` ### Options ``` +--mode Mode preset: fast, default, recall, accurate. [default] --format Output format: fasta, msf, clu. [fasta] --type Sequence type: protein, dna, rna, divergent. [auto] ---gpo Gap open penalty. [auto] ---gpe Gap extension penalty. [auto] ---tgpe Terminal gap extension penalty. [auto] ---ensemble N Run N ensemble alignments. [off] ---refine Refinement: none, all, confident. [none] +--gpo Gap open penalty (overrides preset). [auto] +--gpe Gap extension penalty (overrides preset). [auto] +--tgpe Terminal gap extension penalty (overrides preset). [auto] -n Number of threads. [auto] ``` diff --git a/benchmarks/analysis.py b/benchmarks/analysis.py deleted file mode 100644 index 4302aea..0000000 --- a/benchmarks/analysis.py +++ /dev/null @@ -1,574 +0,0 @@ -"""RV11 alignment structure analysis. - -Compares gap structure and alignment geometry between kalign, reference, -and external tools (mafft, muscle, clustalo) to understand HOW alignments -differ structurally — not just score differences. - -Usage: - # Locally (kalign only, external tools skipped if not installed): - uv run python -m benchmarks.analysis - - # Inside container (has mafft/muscle/clustalo): - python -m benchmarks.analysis - - # Specific dataset: - python -m benchmarks.analysis --dataset balibase_RV11 - - # Write CSV: - python -m benchmarks.analysis --csv benchmarks/results/gap_analysis.csv -""" - -import argparse -import csv -import json -import re -import statistics -import sys -import tempfile -from dataclasses import dataclass, fields -from pathlib import Path -from typing import Dict, List, Optional, Tuple - -from .datasets import balibase_cases, balibase_is_available - -RESULTS_DIR = Path(__file__).parent / "results" - - -# --------------------------------------------------------------------------- -# MSF parser (reference alignments are in GCG MSF format) -# --------------------------------------------------------------------------- - -def parse_msf(path: Path) -> Dict[str, str]: - """Parse a GCG MSF file into {name: aligned_sequence}.""" - text = path.read_text() - - # Split at "//" separator - parts = text.split("//") - if len(parts) < 2: - raise ValueError(f"No // separator found in {path}") - - body = parts[1] - seqs: Dict[str, List[str]] = {} - for line in body.splitlines(): - line = line.strip() - if not line: - continue - tokens = line.split() - if len(tokens) < 2: - continue - name = tokens[0] - # Sequence characters (may contain dots for gaps) - seq_parts = "".join(tokens[1:]) - seqs.setdefault(name, []).append(seq_parts) - - # Join blocks and normalise: dots → dashes, remove whitespace - result = {} - for name, blocks in seqs.items(): - seq = "".join(blocks).replace(".", "-").upper() - result[name] = seq - return result - - -# --------------------------------------------------------------------------- -# FASTA parser (kalign/tool outputs) -# --------------------------------------------------------------------------- - -def parse_fasta(path: Path) -> Dict[str, str]: - """Parse a FASTA file into {name: sequence}.""" - seqs: Dict[str, str] = {} - current = None - parts: List[str] = [] - for line in path.read_text().splitlines(): - line = line.strip() - if line.startswith(">"): - if current is not None: - seqs[current] = "".join(parts).upper() - current = line[1:].split()[0] - parts = [] - elif current is not None: - parts.append(line) - if current is not None: - seqs[current] = "".join(parts).upper() - return seqs - - -# --------------------------------------------------------------------------- -# Gap structure metrics -# --------------------------------------------------------------------------- - -@dataclass -class GapStats: - """Gap structure metrics for one alignment.""" - n_seqs: int - alignment_length: int - mean_seq_length: float # unaligned (non-gap chars) - expansion_factor: float # alignment_length / mean_seq_length - total_gaps: int - gap_fraction: float # total_gaps / (n_seqs * alignment_length) - n_gap_blocks: int - mean_gap_block_len: float - mean_terminal_gap: float # average leading+trailing gap per sequence - mean_internal_gap: float # average total internal gap chars per sequence - n_gappy_columns: int # columns where >50% of sequences have a gap - gappy_column_fraction: float - - -def compute_gap_stats(seqs: Dict[str, str]) -> GapStats: - """Compute gap structure metrics from aligned sequences.""" - sequences = list(seqs.values()) - n_seqs = len(sequences) - if n_seqs == 0: - return GapStats(0, 0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0.0, 0.0, 0, 0.0) - - aln_len = len(sequences[0]) - # Unaligned lengths (non-gap characters) - ungapped_lens = [len(s.replace("-", "")) for s in sequences] - mean_seq_len = statistics.mean(ungapped_lens) - expansion = aln_len / mean_seq_len if mean_seq_len > 0 else 0.0 - - total_gaps = sum(s.count("-") for s in sequences) - total_chars = n_seqs * aln_len - gap_frac = total_gaps / total_chars if total_chars > 0 else 0.0 - - # Gap blocks and lengths - all_block_lens: List[int] = [] - terminal_gaps: List[int] = [] - internal_gaps: List[int] = [] - - for seq in sequences: - # Find all gap blocks - blocks = [(m.start(), m.end()) for m in re.finditer(r"-+", seq)] - all_block_lens.extend(m.end() - m.start() for m in re.finditer(r"-+", seq)) - - # Terminal: leading and trailing - leading = len(seq) - len(seq.lstrip("-")) - trailing = len(seq) - len(seq.rstrip("-")) - terminal_gaps.append(leading + trailing) - - # Internal: everything that's not leading/trailing - internal = sum(e - s for s, e in blocks) - internal -= leading + trailing - internal_gaps.append(max(0, internal)) - - n_gap_blocks = len(all_block_lens) - mean_block_len = statistics.mean(all_block_lens) if all_block_lens else 0.0 - mean_terminal = statistics.mean(terminal_gaps) - mean_internal = statistics.mean(internal_gaps) - - # Gappy columns (>50% gaps) - n_gappy = 0 - for col in range(aln_len): - gaps_in_col = sum(1 for s in sequences if s[col] == "-") - if gaps_in_col > n_seqs / 2: - n_gappy += 1 - - return GapStats( - n_seqs=n_seqs, - alignment_length=aln_len, - mean_seq_length=mean_seq_len, - expansion_factor=expansion, - total_gaps=total_gaps, - gap_fraction=gap_frac, - n_gap_blocks=n_gap_blocks, - mean_gap_block_len=mean_block_len, - mean_terminal_gap=mean_terminal, - mean_internal_gap=mean_internal, - n_gappy_columns=n_gappy, - gappy_column_fraction=n_gappy / aln_len if aln_len > 0 else 0.0, - ) - - -# --------------------------------------------------------------------------- -# Alignment generation helpers -# --------------------------------------------------------------------------- - -def _align_kalign(unaligned: Path, output: Path, seq_type: str) -> None: - """Run kalign via Python API.""" - import kalign - kalign.align_file_to_file( - str(unaligned), str(output), format="fasta", seq_type=seq_type, - ) - - -def _align_external(unaligned: Path, output: Path, tool: str) -> bool: - """Run an external tool. Returns True if successful.""" - import shutil - import subprocess - - if shutil.which(tool) is None: - return False - - try: - if tool == "mafft": - with open(output, "w") as f: - subprocess.run( - ["mafft", "--auto", str(unaligned)], - stdout=f, stderr=subprocess.PIPE, check=True, - ) - elif tool == "clustalo": - subprocess.run( - ["clustalo", "-i", str(unaligned), "-o", str(output), - "--outfmt=fasta", "--force"], - capture_output=True, check=True, - ) - elif tool == "muscle": - subprocess.run( - ["muscle", "-align", str(unaligned), "-output", str(output)], - capture_output=True, check=True, - ) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - - -# --------------------------------------------------------------------------- -# Per-case analysis row -# --------------------------------------------------------------------------- - -@dataclass -class CaseRow: - family: str - method: str - # Scores from results JSON (NaN if not available) - recall: float - precision: float - f1: float - tc: float - # Gap stats - alignment_length: int - expansion_factor: float - gap_fraction: float - n_gap_blocks: int - mean_gap_block_len: float - mean_terminal_gap: float - mean_internal_gap: float - n_gappy_columns: int - gappy_column_fraction: float - - -# --------------------------------------------------------------------------- -# Load frozen scores from full_comparison.json -# --------------------------------------------------------------------------- - -def load_scores(json_path: Path, dataset_filter: str = "balibase_RV11") -> Dict[Tuple[str, str], dict]: - """Load {(family, method_key): scores} from results JSON. - - method_key is 'kalign' for python_api/refine=none, or the external tool name. - """ - with open(json_path) as f: - data = json.load(f) - - scores: Dict[Tuple[str, str], dict] = {} - for r in data["results"]: - if dataset_filter and r["dataset"] != dataset_filter: - continue - - if r["method"] == "python_api" and r["refine"] == "none": - key = (r["family"], "kalign") - elif r["method"] in ("clustalo", "mafft", "muscle"): - key = (r["family"], r["method"]) - else: - continue - - scores[key] = { - "recall": r.get("recall", float("nan")), - "precision": r.get("precision", float("nan")), - "f1": r.get("f1", float("nan")), - "tc": r.get("tc", float("nan")), - } - return scores - - -# --------------------------------------------------------------------------- -# Main analysis -# --------------------------------------------------------------------------- - -def analyse_rv11( - dataset_filter: str = "balibase_RV11", - external_tools: Optional[List[str]] = None, -) -> List[CaseRow]: - """Run gap analysis on all RV11 cases. Returns list of CaseRow.""" - if external_tools is None: - external_tools = ["mafft", "muscle", "clustalo"] - - if not balibase_is_available(): - print("BAliBASE not downloaded. Run: uv run python -m benchmarks --download-only") - return [] - - cases = [c for c in balibase_cases() if c.dataset == dataset_filter] - if not cases: - print(f"No cases found for {dataset_filter}") - return [] - - # Load frozen scores - json_path = RESULTS_DIR / "full_comparison.json" - scores = load_scores(json_path, dataset_filter) if json_path.exists() else {} - - rows: List[CaseRow] = [] - - for case in cases: - print(f" {case.family} ...", end="", flush=True) - - # --- Reference alignment --- - ref_seqs = parse_msf(case.reference) - ref_stats = compute_gap_stats(ref_seqs) - sc = scores.get((case.family, "reference"), {}) - rows.append(CaseRow( - family=case.family, method="reference", - recall=1.0, precision=1.0, f1=1.0, tc=1.0, - alignment_length=ref_stats.alignment_length, - expansion_factor=ref_stats.expansion_factor, - gap_fraction=ref_stats.gap_fraction, - n_gap_blocks=ref_stats.n_gap_blocks, - mean_gap_block_len=ref_stats.mean_gap_block_len, - mean_terminal_gap=ref_stats.mean_terminal_gap, - mean_internal_gap=ref_stats.mean_internal_gap, - n_gappy_columns=ref_stats.n_gappy_columns, - gappy_column_fraction=ref_stats.gappy_column_fraction, - )) - - # --- Kalign --- - with tempfile.TemporaryDirectory() as tmpdir: - kalign_out = Path(tmpdir) / f"{case.family}_kalign.fa" - _align_kalign(case.unaligned, kalign_out, case.seq_type) - kalign_seqs = parse_fasta(kalign_out) - kalign_stats = compute_gap_stats(kalign_seqs) - - sc = scores.get((case.family, "kalign"), {}) - rows.append(CaseRow( - family=case.family, method="kalign", - recall=sc.get("recall", float("nan")), - precision=sc.get("precision", float("nan")), - f1=sc.get("f1", float("nan")), - tc=sc.get("tc", float("nan")), - alignment_length=kalign_stats.alignment_length, - expansion_factor=kalign_stats.expansion_factor, - gap_fraction=kalign_stats.gap_fraction, - n_gap_blocks=kalign_stats.n_gap_blocks, - mean_gap_block_len=kalign_stats.mean_gap_block_len, - mean_terminal_gap=kalign_stats.mean_terminal_gap, - mean_internal_gap=kalign_stats.mean_internal_gap, - n_gappy_columns=kalign_stats.n_gappy_columns, - gappy_column_fraction=kalign_stats.gappy_column_fraction, - )) - - # --- External tools --- - for tool in external_tools: - with tempfile.TemporaryDirectory() as tmpdir: - tool_out = Path(tmpdir) / f"{case.family}_{tool}.fa" - ok = _align_external(case.unaligned, tool_out, tool) - if not ok: - continue - tool_seqs = parse_fasta(tool_out) - tool_stats = compute_gap_stats(tool_seqs) - - sc = scores.get((case.family, tool), {}) - rows.append(CaseRow( - family=case.family, method=tool, - recall=sc.get("recall", float("nan")), - precision=sc.get("precision", float("nan")), - f1=sc.get("f1", float("nan")), - tc=sc.get("tc", float("nan")), - alignment_length=tool_stats.alignment_length, - expansion_factor=tool_stats.expansion_factor, - gap_fraction=tool_stats.gap_fraction, - n_gap_blocks=tool_stats.n_gap_blocks, - mean_gap_block_len=tool_stats.mean_gap_block_len, - mean_terminal_gap=tool_stats.mean_terminal_gap, - mean_internal_gap=tool_stats.mean_internal_gap, - n_gappy_columns=tool_stats.n_gappy_columns, - gappy_column_fraction=tool_stats.gappy_column_fraction, - )) - - print(" done") - - return rows - - -# --------------------------------------------------------------------------- -# Output formatting -# --------------------------------------------------------------------------- - -def print_table(rows: List[CaseRow]) -> None: - """Print a summary table to stdout, grouped by family.""" - if not rows: - return - - families = sorted(set(r.family for r in rows)) - methods = sorted(set(r.method for r in rows)) - - # Per-case comparison table - hdr = f"{'Family':<10} {'Method':<10} {'Recall':>7} {'Prec':>7} {'F1':>7} {'AlnLen':>7} {'Expand':>7} {'GapFrac':>7} {'GapBlk':>7} {'MeanBL':>7} {'TermGap':>7} {'IntGap':>7} {'Gappy%':>7}" - print("\n" + "=" * len(hdr)) - print(hdr) - print("-" * len(hdr)) - - for fam in families: - fam_rows = sorted( - [r for r in rows if r.family == fam], - key=lambda r: (r.method != "reference", r.method != "kalign", r.method), - ) - for r in fam_rows: - rec = f"{r.recall:.3f}" if r.recall == r.recall else " n/a" - pre = f"{r.precision:.3f}" if r.precision == r.precision else " n/a" - f1 = f"{r.f1:.3f}" if r.f1 == r.f1 else " n/a" - print( - f"{r.family:<10} {r.method:<10} {rec:>7} {pre:>7} {f1:>7} " - f"{r.alignment_length:>7} {r.expansion_factor:>7.2f} " - f"{r.gap_fraction:>7.3f} {r.n_gap_blocks:>7} " - f"{r.mean_gap_block_len:>7.1f} {r.mean_terminal_gap:>7.1f} " - f"{r.mean_internal_gap:>7.1f} {r.gappy_column_fraction:>7.3f}" - ) - print() - - # Aggregate summary by method - print("=" * 80) - print("AGGREGATE SUMMARY (means across all families)") - print("-" * 80) - fmt = "{:<10} {:>7} {:>7} {:>7} {:>8} {:>7} {:>7} {:>8} {:>8}" - print(fmt.format("Method", "Recall", "Prec", "F1", "Expand", "GapFrac", "MeanBL", "TermGap", "IntGap")) - print("-" * 80) - - for method in ["reference", "kalign"] + [m for m in methods if m not in ("reference", "kalign")]: - method_rows = [r for r in rows if r.method == method] - if not method_rows: - continue - def safe_mean(vals): - clean = [v for v in vals if v == v] # filter NaN - return statistics.mean(clean) if clean else float("nan") - - rec = safe_mean([r.recall for r in method_rows]) - pre = safe_mean([r.precision for r in method_rows]) - f1 = safe_mean([r.f1 for r in method_rows]) - exp = statistics.mean([r.expansion_factor for r in method_rows]) - gf = statistics.mean([r.gap_fraction for r in method_rows]) - mbl = statistics.mean([r.mean_gap_block_len for r in method_rows]) - tg = statistics.mean([r.mean_terminal_gap for r in method_rows]) - ig = statistics.mean([r.mean_internal_gap for r in method_rows]) - - rec_s = f"{rec:.3f}" if rec == rec else " n/a" - pre_s = f"{pre:.3f}" if pre == pre else " n/a" - f1_s = f"{f1:.3f}" if f1 == f1 else " n/a" - print(fmt.format(method, rec_s, pre_s, f1_s, f"{exp:.2f}", f"{gf:.3f}", f"{mbl:.1f}", f"{tg:.1f}", f"{ig:.1f}")) - - # Correlation analysis: precision vs expansion factor for kalign - kalign_rows = [r for r in rows if r.method == "kalign" and r.precision == r.precision] - if len(kalign_rows) >= 5: - print("\n" + "=" * 80) - print("CORRELATION: kalign precision vs gap metrics") - print("-" * 80) - - # Simple Pearson correlation - def pearson(xs, ys): - n = len(xs) - if n < 3: - return float("nan") - mx, my = statistics.mean(xs), statistics.mean(ys) - num = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) - dx = sum((x - mx) ** 2 for x in xs) ** 0.5 - dy = sum((y - my) ** 2 for y in ys) ** 0.5 - return num / (dx * dy) if dx > 0 and dy > 0 else float("nan") - - prec = [r.precision for r in kalign_rows] - metrics = [ - ("expansion_factor", [r.expansion_factor for r in kalign_rows]), - ("gap_fraction", [r.gap_fraction for r in kalign_rows]), - ("mean_gap_block_len", [r.mean_gap_block_len for r in kalign_rows]), - ("mean_terminal_gap", [r.mean_terminal_gap for r in kalign_rows]), - ("mean_internal_gap", [r.mean_internal_gap for r in kalign_rows]), - ("gappy_column_fraction", [r.gappy_column_fraction for r in kalign_rows]), - ] - for name, vals in metrics: - r = pearson(prec, vals) - print(f" precision vs {name:<25}: r = {r:+.3f}") - - # Also: kalign expansion vs reference expansion - ref_rows = {r.family: r for r in rows if r.method == "reference"} - kalign_dict = {r.family: r for r in kalign_rows} - common = sorted(set(ref_rows) & set(kalign_dict)) - if common: - print(f"\n Expansion factor comparison (kalign vs reference, n={len(common)}):") - over = [f for f in common if kalign_dict[f].expansion_factor > ref_rows[f].expansion_factor * 1.05] - under = [f for f in common if kalign_dict[f].expansion_factor < ref_rows[f].expansion_factor * 0.95] - same = [f for f in common if f not in over and f not in under] - print(f" Over-expanded (>5%): {len(over)}") - print(f" Under-expanded: {len(under)}") - print(f" Similar (+/-5%): {len(same)}") - - # Relative expansion ratio correlated with precision - ratios = [kalign_dict[f].expansion_factor / max(ref_rows[f].expansion_factor, 0.01) for f in common] - precs = [kalign_dict[f].precision for f in common] - r_ratio = pearson(ratios, precs) - print(f" Correlation (expand_ratio vs precision): r = {r_ratio:+.3f}") - - if over: - print(f"\n Worst over-expanded cases (kalign expand / ref expand):") - ranked = sorted(over, key=lambda f: kalign_dict[f].expansion_factor / max(ref_rows[f].expansion_factor, 0.01), reverse=True) - for f in ranked[:10]: - ke = kalign_dict[f].expansion_factor - re = ref_rows[f].expansion_factor - kp = kalign_dict[f].precision - print(f" {f}: kalign={ke:.2f} ref={re:.2f} ratio={ke/re:.2f} precision={kp:.3f}") - - if under: - print(f"\n Worst under-expanded cases (kalign expand / ref expand):") - ranked = sorted(under, key=lambda f: kalign_dict[f].expansion_factor / max(ref_rows[f].expansion_factor, 0.01)) - for f in ranked[:10]: - ke = kalign_dict[f].expansion_factor - re = ref_rows[f].expansion_factor - kp = kalign_dict[f].precision - print(f" {f}: kalign={ke:.2f} ref={re:.2f} ratio={ke/re:.2f} precision={kp:.3f}") - - -def write_csv(rows: List[CaseRow], path: str) -> None: - """Write analysis results to CSV.""" - fieldnames = [f.name for f in fields(CaseRow)] - Path(path).parent.mkdir(parents=True, exist_ok=True) - with open(path, "w", newline="") as f: - writer = csv.DictWriter(f, fieldnames=fieldnames) - writer.writeheader() - for row in rows: - d = {fn: getattr(row, fn) for fn in fieldnames} - writer.writerow(d) - print(f"\nCSV written to {path}") - - -# --------------------------------------------------------------------------- -# CLI entry point -# --------------------------------------------------------------------------- - -def main() -> None: - parser = argparse.ArgumentParser( - description="RV11 alignment structure analysis", - prog="python -m benchmarks.analysis", - ) - parser.add_argument( - "--dataset", default="balibase_RV11", - help="Dataset filter (default: balibase_RV11)", - ) - parser.add_argument( - "--csv", default="", - help="Write results to CSV file", - ) - parser.add_argument( - "--no-external", action="store_true", - help="Skip external tools (mafft, muscle, clustalo)", - ) - args = parser.parse_args() - - external = [] if args.no_external else ["mafft", "muscle", "clustalo"] - - print(f"Analysing {args.dataset} alignment structure...") - rows = analyse_rv11(dataset_filter=args.dataset, external_tools=external) - - if not rows: - sys.exit(1) - - print_table(rows) - - if args.csv: - write_csv(rows, args.csv) - - -if __name__ == "__main__": - main() diff --git a/benchmarks/app.py b/benchmarks/app.py deleted file mode 100644 index 86e4cdb..0000000 --- a/benchmarks/app.py +++ /dev/null @@ -1,603 +0,0 @@ -"""Dash app for running kalign benchmarks and viewing results. - -Usage: - python -m benchmarks.app [--port 8050] -""" - -import argparse -import json -import sys -import threading -from pathlib import Path - -try: - import dash - from dash import dcc, html, dash_table, callback_context - from dash.dependencies import Input, Output, State - import plotly.express as px - import pandas as pd -except ImportError: - print("Dash visualization requires extra dependencies:") - print(" pip install dash plotly pandas") - sys.exit(1) - -from .datasets import DATASETS, get_cases, download_dataset -from .scoring import run_case - -RESULTS_DIR = Path(__file__).parent / "results" -RESULTS_DIR.mkdir(parents=True, exist_ok=True) - -# --------------------------------------------------------------------------- -# Configuration presets — the 4 varieties of kalign -# --------------------------------------------------------------------------- - -CONFIG_PRESETS = { - "Kalign (default)": {"refine": "none", "ensemble": 0}, - "Kalign + Refinement": {"refine": "confident", "ensemble": 0}, - "Ensemble (8 runs)": {"refine": "none", "ensemble": 8}, - "Ensemble (12 runs)": {"refine": "none", "ensemble": 12}, - "Clustal Omega": {"method": "clustalo", "refine": "none", "ensemble": 0}, - "MAFFT": {"method": "mafft", "refine": "none", "ensemble": 0}, - "MUSCLE": {"method": "muscle", "refine": "none", "ensemble": 0}, -} - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _available_datasets(): - """Return list of (name, available, n_cases) tuples.""" - info = [] - for name, ds in DATASETS.items(): - avail = ds["is_available"]() - n = len(ds["cases"]()) if avail else 0 - info.append((name, avail, n)) - return info - - -def _load_json(path): - with open(path) as f: - return json.load(f) - - -_EXTERNAL_LABELS = { - "clustalo": "Clustal Omega", - "mafft": "MAFFT", - "muscle": "MUSCLE", -} - - -def _config_label(row): - """Build a human-readable configuration label from result fields.""" - method = row.get("method", "python_api") - if method in _EXTERNAL_LABELS: - return _EXTERNAL_LABELS[method] - - ensemble = row.get("ensemble", 0) - refine = row.get("refine", "none") - - if ensemble and ensemble > 0: - label = f"Ensemble ({ensemble})" - elif refine and refine != "none": - label = f"Kalign + {refine}" - else: - label = "Kalign" - return label - - -def _results_to_df(results): - """Convert list of AlignmentResult dicts to DataFrame.""" - df = pd.DataFrame(results) - if "error" in df.columns: - df = df[df["error"].isna() | (df["error"] == "None") | (df["error"].isnull())] - # Ensure columns exist (backward compat with old JSON files) - if "ensemble" not in df.columns: - df["ensemble"] = 0 - df["ensemble"] = df["ensemble"].fillna(0).astype(int) - for col in ("recall", "precision", "f1", "tc"): - if col not in df.columns: - df[col] = 0.0 - df[col] = df[col].fillna(0.0) - # Add config label for plotting - df["config"] = df.apply(_config_label, axis=1) - return df - - -def _build_figures(df): - """Build plotly figures from a results DataFrame.""" - figs = [] - if df.empty: - return figs - - # Filter out errors - clean = df[df["sp_score"] > 0].copy() if "sp_score" in df.columns else df - - if clean.empty: - return figs - - # Ensure new columns exist (backward compat with old JSON files) - for col in ("recall", "precision", "f1", "tc"): - if col not in clean.columns: - clean[col] = 0.0 - - # Determine color column: use 'config' if multiple configs, else 'method' - configs = clean["config"].nunique() - color_col = "config" if configs > 1 else "method" - - # bali_score-compatible metrics - has_recall = clean["recall"].sum() > 0 - if has_recall: - # SP score (bali_score compatible) = recall - fig_sp = px.box( - clean, x="dataset", y="recall", color=color_col, - title="SP Score by Dataset (bali_score compatible)", - labels={"recall": "SP Score", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_sp.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("sp_box", fig_sp)) - - # TC score - fig_tc = px.box( - clean, x="dataset", y="tc", color=color_col, - title="TC Score by Dataset", - labels={"tc": "TC Score", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_tc.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("tc_box", fig_tc)) - - # Precision - fig_prec = px.box( - clean, x="dataset", y="precision", color=color_col, - title="Precision by Dataset", - labels={"precision": "Precision", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_prec.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("precision_box", fig_prec)) - - # F1 - fig_f1 = px.box( - clean, x="dataset", y="f1", color=color_col, - title="F1 Score by Dataset", - labels={"f1": "F1", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_f1.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("f1_box", fig_f1)) - else: - # Fallback: legacy SP score (not bali_score compatible) - fig_box = px.box( - clean, x="dataset", y="sp_score", color=color_col, - title="SP Score Distribution by Dataset (legacy)", - labels={"sp_score": "SP Score", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_box.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("sp_box", fig_box)) - - # Strip plot by family - hover_cols = ["family", "wall_time", "refine", "ensemble"] - if has_recall: - hover_cols.extend(["recall", "precision", "f1", "tc"]) - y_strip = "recall" if has_recall else "sp_score" - fig_scatter = px.strip( - clean, x="dataset", y=y_strip, color=color_col, - hover_data=hover_cols, - title="SP Score per Family", - labels={"recall": "SP Score", "sp_score": "SP Score (legacy)", "config": "Configuration"}, - ) - fig_scatter.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("scatter", fig_scatter)) - - # Timing - fig_time = px.box( - clean, x="dataset", y="wall_time", color=color_col, - title="Alignment Time by Dataset", - labels={"wall_time": "Wall Time (s)", "dataset": "Dataset", "config": "Configuration"}, - ) - fig_time.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("timing", fig_time)) - - # Summary table by config x dataset - if configs > 1: - agg_dict = { - "mean_sp": ("sp_score", "mean"), - "median_sp": ("sp_score", "median"), - "mean_time": ("wall_time", "mean"), - "n_cases": ("sp_score", "count"), - } - if has_recall: - agg_dict.update({ - "mean_recall": ("recall", "mean"), - "mean_precision": ("precision", "mean"), - "mean_f1": ("f1", "mean"), - "mean_tc": ("tc", "mean"), - }) - summary = clean.groupby(["config", "dataset"]).agg(**agg_dict).reset_index() - summary = summary.round(3) - y_col = "mean_recall" if has_recall else "mean_sp" - y_label = "Mean SP Score" if has_recall else "Mean SP Score (legacy)" - fig_summary = px.bar( - summary, x="dataset", y=y_col, color="config", - barmode="group", - title=f"{y_label} by Dataset and Configuration", - labels={y_col: y_label, "dataset": "Dataset", "config": "Configuration"}, - ) - fig_summary.update_layout(legend=dict(orientation="h", y=-0.2)) - figs.append(("summary_bar", fig_summary)) - - return figs - - -# --------------------------------------------------------------------------- -# State shared between callbacks and the benchmark thread -# --------------------------------------------------------------------------- - -_run_state = { - "running": False, - "progress": "", - "results": [], - "done": False, -} - - -def _run_in_thread(dataset, method, binary, max_cases, n_threads, configs): - """Run benchmarks in a background thread so the UI stays responsive.""" - global _run_state - _run_state["running"] = True - _run_state["done"] = False - _run_state["results"] = [] - _run_state["progress"] = f"Loading {dataset} cases..." - - try: - cases = get_cases(dataset, max_cases=max_cases if max_cases > 0 else None) - if not cases: - _run_state["progress"] = f"No cases found for {dataset}. Try downloading first." - _run_state["running"] = False - _run_state["done"] = True - return - - total = len(cases) * len(configs) - done = 0 - - for cfg_name, cfg in configs: - cfg_method = cfg.get("method", method) - refine = cfg["refine"] - ensemble = cfg["ensemble"] - - for case in cases: - done += 1 - _run_state["progress"] = ( - f"[{done}/{total}] {cfg_name}: {case.family}..." - ) - result = run_case( - case, method=cfg_method, binary=binary, - n_threads=n_threads, refine=refine, ensemble=ensemble, - ) - _run_state["results"].append(result.to_dict()) - - # Auto-save - import time as _time - save_path = RESULTS_DIR / f"run_{_time.strftime('%Y%m%d_%H%M%S')}.json" - data = { - "timestamp": _time.strftime("%Y-%m-%dT%H:%M:%S"), - "results": _run_state["results"], - } - with open(save_path, "w") as f: - json.dump(data, f, indent=2) - - _run_state["progress"] = f"Done! {done} alignments scored. Saved to {save_path.name}" - except Exception as e: - _run_state["progress"] = f"Error: {e}" - finally: - _run_state["running"] = False - _run_state["done"] = True - - -# --------------------------------------------------------------------------- -# App layout -# --------------------------------------------------------------------------- - -def create_app(): - app = dash.Dash(__name__) - app.title = "Kalign Benchmarks" - - # Discover saved result files - def _saved_files(): - return sorted(RESULTS_DIR.glob("*.json"), reverse=True) - - ds_info = _available_datasets() - - app.layout = html.Div([ - html.H1("Kalign Benchmark Dashboard"), - - # --- Run controls --- - html.Div([ - html.H3("Run Benchmark"), - html.Div([ - # Row 1: Dataset, method, binary - html.Div([ - html.Div([ - html.Label("Dataset"), - dcc.Dropdown( - id="dataset-dropdown", - options=[ - {"label": f"{name} ({'available' if avail else 'needs download'}, {n} cases)", - "value": name} - for name, avail, n in ds_info - ], - value="balibase", - ), - ], style={"width": "30%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Label("Method"), - dcc.RadioItems( - id="method-radio", - options=[ - {"label": " Python API", "value": "python_api"}, - {"label": " C binary", "value": "cli"}, - ], - value="python_api", - ), - ], style={"width": "12%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Label("C binary path"), - dcc.Input(id="binary-input", type="text", value="build/src/kalign", - style={"width": "160px"}), - ], style={"width": "18%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Label("Max cases (0=all)"), - dcc.Input(id="max-cases-input", type="number", value=0, min=0, style={"width": "80px"}), - ], style={"width": "10%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Label("Threads"), - dcc.Input(id="threads-input", type="number", value=1, min=1, style={"width": "60px"}), - ], style={"width": "8%", "display": "inline-block", "verticalAlign": "top"}), - ]), - - html.Hr(style={"margin": "10px 0"}), - - # Row 2: Configuration presets - html.Div([ - html.Div([ - html.Label("Configurations to run"), - dcc.Checklist( - id="config-checklist", - options=[{"label": f" {name}", "value": name} - for name in CONFIG_PRESETS], - value=["Kalign (default)"], - style={"lineHeight": "2"}, - ), - ], style={"width": "35%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Label("Custom ensemble runs"), - dcc.Input(id="custom-ensemble-input", type="number", value=8, min=2, max=32, - style={"width": "80px"}), - html.Br(), - html.Label("Custom refine mode", style={"marginTop": "8px"}), - dcc.Dropdown( - id="custom-refine-dropdown", - options=[ - {"label": "none", "value": "none"}, - {"label": "confident", "value": "confident"}, - {"label": "all", "value": "all"}, - ], - value="none", - style={"width": "140px"}, - ), - html.Button("+ Add custom config", id="add-custom-btn", n_clicks=0, - style={"marginTop": "8px"}), - ], style={"width": "25%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Br(), - html.Button("Download Dataset", id="download-btn", n_clicks=0, - style={"marginRight": "10px"}), - html.Br(), html.Br(), - html.Button("Run Benchmark", id="run-btn", n_clicks=0, - style={"backgroundColor": "#4CAF50", "color": "white", - "border": "none", "padding": "12px 24px", "cursor": "pointer", - "fontSize": "14px"}), - ], style={"width": "20%", "display": "inline-block", "verticalAlign": "top"}), - ]), - ]), - html.Div(id="progress-text", style={"marginTop": "10px", "fontStyle": "italic"}), - dcc.Interval(id="progress-interval", interval=1000, n_intervals=0, disabled=True), - # Hidden store for custom configs - dcc.Store(id="custom-configs-store", data=[]), - ], style={"padding": "15px", "backgroundColor": "#f5f5f5", "borderRadius": "8px", "marginBottom": "20px"}), - - # --- Load saved results --- - html.Div([ - html.H3("View Results"), - html.Div([ - html.Div([ - html.Label("Saved result files"), - dcc.Dropdown(id="results-dropdown", multi=True), - ], style={"width": "60%", "display": "inline-block", "verticalAlign": "top", "marginRight": "2%"}), - html.Div([ - html.Br(), - html.Button("Refresh file list", id="refresh-btn", n_clicks=0, - style={"marginRight": "10px"}), - html.Button("Load & Plot", id="load-btn", n_clicks=0), - ], style={"width": "30%", "display": "inline-block", "verticalAlign": "top"}), - ]), - ], style={"padding": "15px", "backgroundColor": "#f0f8ff", "borderRadius": "8px", "marginBottom": "20px"}), - - # --- Charts --- - html.Div(id="charts-container"), - - # --- Table --- - html.Div(id="table-container"), - - ], style={"maxWidth": "1200px", "margin": "0 auto", "padding": "20px", "fontFamily": "sans-serif"}) - - # --- Callbacks --- - - @app.callback( - Output("results-dropdown", "options"), - Input("refresh-btn", "n_clicks"), - Input("progress-interval", "n_intervals"), - ) - def refresh_file_list(_n1, _n2): - files = _saved_files() - return [{"label": f.name, "value": str(f)} for f in files] - - @app.callback( - Output("custom-configs-store", "data"), - Output("config-checklist", "options"), - Output("config-checklist", "value"), - Input("add-custom-btn", "n_clicks"), - State("custom-ensemble-input", "value"), - State("custom-refine-dropdown", "value"), - State("custom-configs-store", "data"), - State("config-checklist", "options"), - State("config-checklist", "value"), - prevent_initial_call=True, - ) - def add_custom_config(_n, ensemble_n, refine, custom_cfgs, options, selected): - """Add a custom configuration to the checklist.""" - ensemble_n = ensemble_n or 0 - refine = refine or "none" - - if ensemble_n > 0: - name = f"Ensemble ({ensemble_n}) + {refine}" - else: - name = f"Custom (refine={refine})" - - cfg = {"refine": refine, "ensemble": ensemble_n} - - # Avoid duplicates - for existing in custom_cfgs: - if existing["name"] == name: - return custom_cfgs, options, selected - - custom_cfgs.append({"name": name, **cfg}) - options.append({"label": f" {name}", "value": name}) - selected.append(name) - return custom_cfgs, options, selected - - @app.callback( - Output("progress-text", "children"), - Output("progress-interval", "disabled"), - Input("run-btn", "n_clicks"), - Input("download-btn", "n_clicks"), - Input("progress-interval", "n_intervals"), - State("dataset-dropdown", "value"), - State("method-radio", "value"), - State("binary-input", "value"), - State("max-cases-input", "value"), - State("threads-input", "value"), - State("config-checklist", "value"), - State("custom-configs-store", "data"), - prevent_initial_call=True, - ) - def handle_run_or_download(run_clicks, dl_clicks, _n_intervals, - dataset, method, binary, max_cases, n_threads, - selected_configs, custom_cfgs): - triggered = callback_context.triggered_id - - if triggered == "download-btn" and not _run_state["running"]: - try: - download_dataset(dataset) - return f"Downloaded {dataset}.", True - except Exception as e: - return f"Download error: {e}", True - - if triggered == "run-btn" and not _run_state["running"]: - if not selected_configs: - return "Select at least one configuration.", True - - # Build config list from presets + custom - configs = [] - custom_by_name = {c["name"]: c for c in (custom_cfgs or [])} - for name in selected_configs: - if name in CONFIG_PRESETS: - configs.append((name, CONFIG_PRESETS[name])) - elif name in custom_by_name: - c = custom_by_name[name] - configs.append((name, {"refine": c["refine"], "ensemble": c["ensemble"]})) - - if not configs: - return "No valid configurations selected.", True - - t = threading.Thread( - target=_run_in_thread, - args=(dataset, method, binary or "build/src/kalign", - max_cases or 0, n_threads or 1, configs), - daemon=True, - ) - t.start() - return f"Starting {len(configs)} configuration(s)...", False - - # Polling progress - if _run_state["running"]: - return _run_state["progress"], False - if _run_state["done"]: - return _run_state["progress"], True - - return dash.no_update, dash.no_update - - @app.callback( - Output("charts-container", "children"), - Output("table-container", "children"), - Input("load-btn", "n_clicks"), - Input("progress-interval", "n_intervals"), - State("results-dropdown", "value"), - prevent_initial_call=True, - ) - def update_charts(load_clicks, _n_intervals, selected_files): - triggered = callback_context.triggered_id - - # If benchmark just finished, show those results immediately - if triggered == "progress-interval" and _run_state["done"] and _run_state["results"]: - df = _results_to_df(_run_state["results"]) - elif triggered == "load-btn" and selected_files: - all_results = [] - for path in selected_files: - data = _load_json(path) - for r in data["results"]: - r["source"] = Path(path).stem - all_results.extend(data["results"]) - df = _results_to_df(all_results) - else: - return dash.no_update, dash.no_update - - if df.empty: - return html.P("No results to display."), "" - - charts = [] - for fig_id, fig in _build_figures(df): - charts.append(dcc.Graph(id=fig_id, figure=fig)) - - # Table - display_cols = [c for c in df.columns if c not in ("error",)] - table = dash_table.DataTable( - data=df.to_dict("records"), - columns=[{"name": c, "id": c} for c in display_cols], - filter_action="native", - sort_action="native", - page_size=50, - style_table={"overflowX": "auto"}, - style_cell={"textAlign": "left", "padding": "5px"}, - style_header={"fontWeight": "bold"}, - ) - - return charts, html.Div([html.H3("Results Table"), table]) - - return app - - -def main(): - parser = argparse.ArgumentParser( - description="Kalign benchmark dashboard", - prog="python -m benchmarks.app", - ) - parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)") - parser.add_argument("--port", type=int, default=8050, help="Port (default: 8050)") - args = parser.parse_args() - - app = create_app() - print(f"Starting dashboard at http://{args.host}:{args.port}") - app.run(debug=False, host=args.host, port=args.port) - - -if __name__ == "__main__": - main() diff --git a/benchmarks/combined_improvements.py b/benchmarks/combined_improvements.py deleted file mode 100644 index 349f3ec..0000000 --- a/benchmarks/combined_improvements.py +++ /dev/null @@ -1,301 +0,0 @@ -"""Measure cumulative/additive effect of kalign improvements on BAliBASE. - -Tests individual features and their combinations to determine additivity: - Individual features (from baseline): - 1. baseline: vsm_amax=0, no extras - 2. +vsm: vsm_amax=2.0 only - 3. +ref: refine=confident only (vsm=0) - 4. +re1: realign=1 only (vsm=0) - Stacking (additive): - 5. +vsm+ref: vsm + refine - 6. +vsm+re1: vsm + realign=1 (no refine) - 7. +vsm+ref+re1: vsm + refine + realign=1 - Ensemble path: - 8. +ens3: ensemble=3 (uses vsm, internal post-refine) - 9. +ens5: ensemble=5 - 10. +ens5+adapt: ensemble=5 + adaptive_budget - -Usage: - uv run python -m benchmarks.combined_improvements -j 4 - uv run python -m benchmarks.combined_improvements -j 4 --categories RV11 RV12 -""" - -import argparse -import json -import statistics -import tempfile -import time -from collections import defaultdict -from concurrent.futures import ProcessPoolExecutor, as_completed -from pathlib import Path - -import kalign -from .datasets import get_cases -from .scoring import parse_balibase_xml - - -CONFIGS = [ - # --- Individual features (from baseline) --- - { - "name": "baseline", - "desc": "old defaults (vsm=0, no extras)", - "vsm_amax": 0.0, - "dist_scale": 0.0, - "refine": "none", - "ensemble": 0, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+vsm", - "desc": "vsm_amax=2.0 only", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "none", - "ensemble": 0, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+ref", - "desc": "refine=confident only (vsm=0)", - "vsm_amax": 0.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 0, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+re1", - "desc": "realign=1 only (vsm=0)", - "vsm_amax": 0.0, - "dist_scale": 0.0, - "refine": "none", - "ensemble": 0, - "adaptive_budget": False, - "realign": 1, - }, - # --- Stacking (additive combinations) --- - { - "name": "+vsm+ref", - "desc": "vsm + refine", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 0, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+vsm+re1", - "desc": "vsm + realign=1 (no refine)", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "none", - "ensemble": 0, - "adaptive_budget": False, - "realign": 1, - }, - { - "name": "+vsm+ref+re1", - "desc": "vsm + refine + realign=1 (full stack)", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 0, - "adaptive_budget": False, - "realign": 1, - }, - # --- Ensemble path --- - { - "name": "+ens3", - "desc": "ensemble=3 (uses vsm, internal post-refine)", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 3, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+ens5", - "desc": "ensemble=5 (uses vsm, internal post-refine)", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 5, - "adaptive_budget": False, - "realign": 0, - }, - { - "name": "+ens5+adapt", - "desc": "ensemble=5 + adaptive_budget", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 5, - "adaptive_budget": True, - "realign": 0, - }, - # --- Ensemble + post-realign --- - { - "name": "+ens3+re1", - "desc": "ensemble=3 + post-ensemble realign=1", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 3, - "adaptive_budget": False, - "realign": 1, - }, - { - "name": "+ens5+re1", - "desc": "ensemble=5 + post-ensemble realign=1", - "vsm_amax": 2.0, - "dist_scale": 0.0, - "refine": "confident", - "ensemble": 5, - "adaptive_budget": False, - "realign": 1, - }, -] - - -def _score_case(case, output_path): - xml_path = case.reference.with_suffix(".xml") - if xml_path.exists(): - mask = parse_balibase_xml(xml_path) - return kalign.compare_detailed(str(case.reference), str(output_path), column_mask=mask) - return kalign.compare_detailed(str(case.reference), str(output_path)) - - -def _run_one(args): - case, config = args - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - t0 = time.perf_counter() - kalign.align_file_to_file( - str(case.unaligned), tmp_path, format="fasta", - seq_type=case.seq_type, - vsm_amax=config["vsm_amax"], - dist_scale=config["dist_scale"], - refine=config["refine"], - ensemble=config["ensemble"], - adaptive_budget=config["adaptive_budget"], - realign=config.get("realign", 0), - ) - wall_time = time.perf_counter() - t0 - sp_score = kalign.compare(str(case.reference), tmp_path) - scores = _score_case(case, tmp_path) - return { - "family": case.family, "dataset": case.dataset, - "config": config["name"], - "sp_score": sp_score, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - "wall_time": wall_time, - } - except Exception as e: - return { - "family": case.family, "dataset": case.dataset, - "config": config["name"], - "sp_score": 0, "recall": 0, "precision": 0, "f1": 0, "tc": 0, - "wall_time": 0, "error": str(e), - } - finally: - Path(tmp_path).unlink(missing_ok=True) - - -def main(): - parser = argparse.ArgumentParser(description="Combined improvements sweep on BAliBASE") - parser.add_argument("-j", "--parallel", type=int, default=4) - parser.add_argument("--max-cases", type=int, default=0) - parser.add_argument("--categories", nargs="*", default=None, - help="BAliBASE categories (e.g. RV11 RV12)") - args = parser.parse_args() - - cases = get_cases("balibase", max_cases=args.max_cases if args.max_cases else None) - if args.categories: - cats = [c.upper() for c in args.categories] - cases = [c for c in cases if any(cat in c.dataset.upper() for cat in cats)] - - print(f"{len(cases)} BAliBASE cases x {len(CONFIGS)} configs = {len(cases) * len(CONFIGS)} tasks") - - tasks = [(case, config) for case in cases for config in CONFIGS] - - t0 = time.perf_counter() - results = [] - done = 0 - with ProcessPoolExecutor(max_workers=args.parallel) as pool: - futures = {pool.submit(_run_one, t): t for t in tasks} - for f in as_completed(futures): - done += 1 - r = f.result() - results.append(r) - if done % 100 == 0: - print(f" {done}/{len(tasks)} ({time.perf_counter()-t0:.0f}s)") - - elapsed = time.perf_counter() - t0 - print(f"All done in {elapsed:.0f}s\n") - - # Overall summary - print("=" * 90) - print("OVERALL") - print("=" * 90) - _print_config_table(results) - - # Per-category - all_cats = sorted({r["dataset"] for r in results}) - for cat in all_cats: - cat_results = [r for r in results if r["dataset"] == cat] - n_cases = len(cat_results) // len(CONFIGS) - cat_label = cat.replace("balibase_", "") - print(f"\n{'=' * 90}") - print(f"{cat_label} ({n_cases} cases)") - print(f"{'=' * 90}") - _print_config_table(cat_results) - - # Save - out = Path("benchmarks/results/combined_improvements.json") - out.parent.mkdir(parents=True, exist_ok=True) - with open(out, "w") as f: - json.dump({"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"), "results": results}, f, indent=2) - print(f"\nSaved to {out}") - - -def _print_config_table(results): - config_names = [c["name"] for c in CONFIGS] - groups = defaultdict(list) - for r in results: - if "error" not in r: - groups[r["config"]].append(r) - - print(f"{'Config':<16} {'SP':>8} {'Recall':>8} {'Prec':>8} {'F1':>8} {'TC':>8} {'Time(s)':>9} {'dSP':>6} {'dF1':>6}") - print("-" * 92) - baseline_sp = None - baseline_f1 = None - for name in config_names: - entries = groups.get(name, []) - if not entries: - continue - sp = statistics.mean(r["sp_score"] for r in entries) - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - total_time = sum(r["wall_time"] for r in entries) - if baseline_f1 is None: - baseline_sp = sp - baseline_f1 = f1 - dsp = "" - df1 = "" - else: - dsp = f"{sp - baseline_sp:+.1f}" - df1 = f"{f1 - baseline_f1:+.3f}" - print(f"{name:<16} {sp:>8.1f} {rec:>8.3f} {prec:>8.3f} {f1:>8.3f} {tc:>8.3f} {total_time:>9.1f} {dsp:>6} {df1:>6}") - - -if __name__ == "__main__": - main() diff --git a/benchmarks/datasets.py b/benchmarks/datasets.py index da7aca2..776b7f1 100644 --- a/benchmarks/datasets.py +++ b/benchmarks/datasets.py @@ -33,9 +33,20 @@ class BenchmarkCase: # --------------------------------------------------------------------------- BALIBASE_URL = "http://www.lbgi.fr/balibase/BalibaseDownload/BAliBASE_R1-5.tar.gz" +BALIBASE_URL_FALLBACK = "https://web.archive.org/web/20231117003408/https://www.lbgi.fr/balibase/BalibaseDownload/BAliBASE_R1-5.tar.gz" BALIBASE_DIR = DOWNLOADS_DIR / "bb3_release" +def _validate_tarball(path: Path) -> bool: + """Check that a file is a valid gzip tarball (not an HTML error page).""" + try: + with tarfile.open(path) as tf: + tf.getnames() + return True + except (tarfile.ReadError, tarfile.CompressionError, EOFError): + return False + + def balibase_download() -> None: """Download and extract BAliBASE R1-5.""" DOWNLOADS_DIR.mkdir(parents=True, exist_ok=True) @@ -44,12 +55,37 @@ def balibase_download() -> None: if BALIBASE_DIR.exists(): return - print(f"Downloading BAliBASE from {BALIBASE_URL} ...") - urllib.request.urlretrieve(BALIBASE_URL, tarball) + if tarball.exists() and not _validate_tarball(tarball): + print(f" Removing corrupt tarball: {tarball}") + tarball.unlink() + + if not tarball.exists(): + for url in [BALIBASE_URL, BALIBASE_URL_FALLBACK]: + try: + print(f"Downloading BAliBASE from {url} ...") + urllib.request.urlretrieve(url, tarball) + if not _validate_tarball(tarball): + print(f" Downloaded file is not a valid tarball (got HTML error page?)") + tarball.unlink() + continue + break + except (urllib.error.HTTPError, urllib.error.URLError) as e: + print(f" Failed: {e}") + if tarball.exists(): + tarball.unlink() + continue + else: + raise RuntimeError( + "Could not download BAliBASE from any URL.\n" + "Please download manually and place at:\n" + f" {tarball}\n" + "Or extract bb3_release/ into:\n" + f" {DOWNLOADS_DIR}/" + ) + print("Extracting ...") with tarfile.open(tarball) as tf: tf.extractall(DOWNLOADS_DIR) - tarball.unlink() def balibase_is_available() -> bool: @@ -107,8 +143,22 @@ def bralibase_download() -> None: if target.exists(): continue tarball = DOWNLOADS_DIR / f"{name}.tar.gz" - print(f"Downloading BRAliBASE {name} from {url} ...") - urllib.request.urlretrieve(url, tarball) + if tarball.exists() and not _validate_tarball(tarball): + print(f" Removing corrupt tarball: {tarball}") + tarball.unlink() + if not tarball.exists(): + print(f"Downloading BRAliBASE {name} from {url} ...") + try: + urllib.request.urlretrieve(url, tarball) + except (urllib.error.HTTPError, urllib.error.URLError) as e: + raise RuntimeError(f"Could not download BRAliBASE {name}: {e}") + if not _validate_tarball(tarball): + tarball.unlink() + raise RuntimeError( + f"Downloaded BRAliBASE {name} is not a valid tarball.\n" + f"Please download manually from:\n {url}\n" + f"and place at:\n {tarball}" + ) print("Extracting ...") with tarfile.open(tarball) as tf: tf.extractall(DOWNLOADS_DIR) @@ -239,10 +289,167 @@ def balifam_cases() -> List[BenchmarkCase]: return cases +# --------------------------------------------------------------------------- +# MDSA (Multiple DNA Sequence Alignments — Carroll et al. 2007) +# --------------------------------------------------------------------------- + +MDSA_BASE_URL = "https://dna.cs.byu.edu/mdsas/tarred" +MDSA_DIR = DOWNLOADS_DIR / "mdsa" +# Skip PREFAB (all pairwise, 2 seqs — not useful for MSA benchmarking) +MDSA_DATABASES = ["balibase", "oxbench", "smart"] +MDSA_VERSION = "all" # "100s" is too small (400 cases); "all" has 1,869 usable cases +MDSA_MAX_SEQS = 15 # Cap for optimizer feasibility (~325 cases, ~3-4 days for 100 gens) +MDSA_MAX_ALN_LEN = 400 # Skip families with very long reference alignments + + +def mdsa_download() -> None: + """Download and extract MDSA DNA alignment benchmark datasets.""" + MDSA_DIR.mkdir(parents=True, exist_ok=True) + + for db in MDSA_DATABASES: + db_dir = MDSA_DIR / f"{db}_mdsa_{MDSA_VERSION}" + if db_dir.exists() and any(db_dir.glob("*.afa")): + continue + + tarball = MDSA_DIR / f"{db}_mdsa_{MDSA_VERSION}.tar.gz" + if tarball.exists() and not _validate_tarball(tarball): + print(f" Removing corrupt tarball: {tarball}") + tarball.unlink() + if not tarball.exists(): + url = f"{MDSA_BASE_URL}/{db}_mdsa_{MDSA_VERSION}.tar.gz" + print(f"Downloading MDSA {db} from {url} ...") + urllib.request.urlretrieve(url, tarball) + if not _validate_tarball(tarball): + tarball.unlink() + raise RuntimeError( + f"Downloaded MDSA {db} is not a valid tarball.\n" + f"Please download manually from:\n {url}\n" + f"and place at:\n {tarball}" + ) + + print(f"Extracting {db} ...") + with tarfile.open(tarball) as tf: + tf.extractall(MDSA_DIR) + + # Generate unaligned files by stripping gaps from reference .afa files + unaligned_dir = MDSA_DIR / "unaligned" + unaligned_dir.mkdir(exist_ok=True) + + for db in MDSA_DATABASES: + db_dir = MDSA_DIR / f"{db}_mdsa_{MDSA_VERSION}" + if not db_dir.exists(): + continue + db_unaligned = unaligned_dir / db + db_unaligned.mkdir(exist_ok=True) + for afa in sorted(db_dir.glob("*.afa")): + out = db_unaligned / afa.name + if out.exists(): + continue + _strip_gaps_fasta(afa, out) + + print(f"MDSA ready: {sum(1 for _ in unaligned_dir.rglob('*.afa'))} unaligned files") + + +def _strip_gaps_fasta(ref_path: Path, out_path: Path) -> None: + """Read aligned FASTA, strip gap characters, write unaligned FASTA.""" + sequences = [] + current_header = None + current_seq = [] + + with open(ref_path) as f: + for line in f: + line = line.rstrip("\n") + if line.startswith(">"): + if current_header is not None: + seq = "".join(current_seq).replace("-", "").replace(".", "") + sequences.append((current_header, seq)) + current_header = line + current_seq = [] + else: + current_seq.append(line) + if current_header is not None: + seq = "".join(current_seq).replace("-", "").replace(".", "") + sequences.append((current_header, seq)) + + with open(out_path, "w") as f: + for header, seq in sequences: + f.write(f"{header}\n{seq}\n") + + +def mdsa_is_available() -> bool: + unaligned_dir = MDSA_DIR / "unaligned" + return unaligned_dir.exists() and any(unaligned_dir.rglob("*.afa")) + + +def mdsa_cases() -> List[BenchmarkCase]: + """Discover MDSA test cases. + + Reference = original .afa (aligned FASTA) + Unaligned = gap-stripped version in unaligned/{db}/*.afa + Skips PREFAB (pairwise) and very large families (>MDSA_MAX_SEQS). + """ + cases = [] + unaligned_dir = MDSA_DIR / "unaligned" + + for db in MDSA_DATABASES: + ref_dir = MDSA_DIR / f"{db}_mdsa_{MDSA_VERSION}" + ua_dir = unaligned_dir / db + + if not ref_dir.exists() or not ua_dir.exists(): + continue + + for ref in sorted(ref_dir.glob("*.afa")): + unaligned = ua_dir / ref.name + if not unaligned.exists(): + continue + + # Count sequences and skip very large families + n_seqs = sum(1 for line in open(ref) if line.startswith(">")) + if n_seqs > MDSA_MAX_SEQS: + continue + # Skip trivial pairwise cases + if n_seqs < 3: + continue + # Skip families with very long alignments + aln_len = max( + (len(line.strip()) for line in open(ref) if not line.startswith(">")), + default=0, + ) + if aln_len > MDSA_MAX_ALN_LEN: + continue + + cases.append( + BenchmarkCase( + family=ref.stem, + dataset=f"mdsa_{db}", + unaligned=unaligned, + reference=ref, + seq_type="dna", + ) + ) + + return cases + + # --------------------------------------------------------------------------- # Registry # --------------------------------------------------------------------------- +def _nucleotide_download() -> None: + """Download both BRAliBASE (RNA) and MDSA (DNA).""" + bralibase_download() + mdsa_download() + + +def _nucleotide_is_available() -> bool: + return bralibase_is_available() and mdsa_is_available() + + +def _nucleotide_cases() -> List[BenchmarkCase]: + """Combined BRAliBASE + MDSA cases for unified nucleotide optimization.""" + return bralibase_cases() + mdsa_cases() + + DATASETS = { "balibase": { "download": balibase_download, @@ -259,6 +466,16 @@ def balifam_cases() -> List[BenchmarkCase]: "is_available": balifam_is_available, "cases": balifam_cases, }, + "mdsa": { + "download": mdsa_download, + "is_available": mdsa_is_available, + "cases": mdsa_cases, + }, + "nucleotide": { + "download": _nucleotide_download, + "is_available": _nucleotide_is_available, + "cases": _nucleotide_cases, + }, } diff --git a/benchmarks/downstream/utils.py b/benchmarks/downstream/utils.py index ba27088..b97930b 100644 --- a/benchmarks/downstream/utils.py +++ b/benchmarks/downstream/utils.py @@ -449,24 +449,17 @@ def _parse_fasta_string(text: str) -> tuple[list[str], list[str]]: def run_kalign( input_fasta: Path, - ensemble: int = 0, + mode: str = "default", seq_type: str = "auto", - tgpo: float | None = None, - terminal_dist_scale: float | None = None, - refine: str | None = None, - vsm_amax: float | None = None, - realign: int | None = None, - consistency: int | None = None, - consistency_weight: float | None = None, ) -> AlignResult: - """Run kalign via the Python API. + """Run kalign via the Python API using mode presets. Parameters ---------- input_fasta : Path Path to unaligned FASTA. - ensemble : int - Number of ensemble runs (0 = off). + mode : str + Alignment mode: "fast", "default", "accurate". seq_type : str Sequence type passed to ``kalign.align_from_file``. @@ -477,26 +470,10 @@ def run_kalign( import kalign as _kalign start = time.perf_counter() - extra = {} - if tgpo is not None: - extra["tgpo"] = tgpo - if terminal_dist_scale is not None: - extra["terminal_dist_scale"] = terminal_dist_scale - if refine is not None: - extra["refine"] = refine - if vsm_amax is not None: - extra["vsm_amax"] = vsm_amax - if realign is not None: - extra["realign"] = realign - if consistency is not None: - extra["consistency"] = consistency - if consistency_weight is not None: - extra["consistency_weight"] = consistency_weight result = _kalign.align_from_file( str(input_fasta), seq_type=seq_type, - ensemble=ensemble, - **extra, + mode=mode, ) wall = time.perf_counter() - start diff --git a/benchmarks/external_balibase.py b/benchmarks/external_balibase.py deleted file mode 100644 index d2f7109..0000000 --- a/benchmarks/external_balibase.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Run external tools (mafft, muscle, clustalo) on BAliBASE inside container. - -Scores against references using kalign's compare_detailed with XML masks. - -Usage (inside container): - python -m benchmarks.external_balibase -""" - -import json -import shutil -import statistics -import subprocess -import tempfile -import time -from collections import defaultdict -from pathlib import Path - -import kalign -from .datasets import get_cases -from .scoring import parse_balibase_xml - - -def _run_external(tool, unaligned, output): - """Run an external alignment tool.""" - try: - if tool == "mafft": - with open(output, "w") as f: - subprocess.run( - ["mafft", "--auto", str(unaligned)], - stdout=f, stderr=subprocess.PIPE, check=True, - ) - elif tool == "clustalo": - subprocess.run( - ["clustalo", "-i", str(unaligned), "-o", str(output), - "--outfmt=fasta", "--force"], - capture_output=True, check=True, - ) - elif tool == "muscle": - subprocess.run( - ["muscle", "-align", str(unaligned), "-output", str(output)], - capture_output=True, check=True, - ) - return True - except (subprocess.CalledProcessError, FileNotFoundError): - return False - - -def _score_case(case, output_path): - xml_path = case.reference.with_suffix(".xml") - if xml_path.exists(): - mask = parse_balibase_xml(xml_path) - return kalign.compare_detailed(str(case.reference), str(output_path), column_mask=mask) - return kalign.compare_detailed(str(case.reference), str(output_path)) - - -def main(): - tools = [] - for t in ["mafft", "muscle", "clustalo"]: - if shutil.which(t): - tools.append(t) - else: - print(f" {t}: not found, skipping") - - if not tools: - print("No external tools found. Run inside container.") - return - - print(f"Tools: {tools}") - - cases = get_cases("balibase") - print(f"{len(cases)} BAliBASE cases") - - results = [] - n_tasks = len(tools) * len(cases) - done = 0 - t0 = time.perf_counter() - - for tool in tools: - print(f"\n Tool: {tool}", flush=True) - for case in cases: - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - t1 = time.perf_counter() - ok = _run_external(tool, case.unaligned, tmp_path) - wall = time.perf_counter() - t1 - if ok: - scores = _score_case(case, tmp_path) - results.append({ - "family": case.family, "dataset": case.dataset, - "method": tool, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - "wall_time": wall, - }) - else: - results.append({ - "family": case.family, "dataset": case.dataset, - "method": tool, - "recall": 0, "precision": 0, "f1": 0, "tc": 0, - "wall_time": 0, "error": f"{tool} failed", - }) - finally: - Path(tmp_path).unlink(missing_ok=True) - - done += 1 - if done % 20 == 0: - elapsed = time.perf_counter() - t0 - eta = elapsed / done * (n_tasks - done) - print(f" {done}/{n_tasks} ({elapsed:.0f}s, ETA {eta:.0f}s)", flush=True) - - elapsed = time.perf_counter() - t0 - print(f"\nAll done in {elapsed:.0f}s") - - # Summary - groups = defaultdict(list) - for r in results: - groups[r["method"]].append(r) - - print(f"\n{'Method':>12} {'Recall':>8} {'Prec':>8} {'F1':>8} {'TC':>8} {'Time':>7}") - print("-" * 60) - for tool in tools: - entries = [r for r in groups[tool] if "error" not in r] - if not entries: - print(f"{tool:>12} (no results)") - continue - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - wt = sum(r["wall_time"] for r in entries) - print(f"{tool:>12} {rec:>8.3f} {prec:>8.3f} {f1:>8.3f} {tc:>8.3f} {wt:>6.1f}s") - - # Per-category - all_cats = sorted({r["dataset"].replace("balibase_", "") for r in results}) - for cat in all_cats: - cat_results = [r for r in results if cat in r["dataset"]] - cat_groups = defaultdict(list) - for r in cat_results: - cat_groups[r["method"]].append(r) - n = len(cat_groups.get(tools[0], [])) - print(f"\n=== {cat} ({n} cases) ===") - print(f"{'Method':>12} {'Recall':>8} {'Prec':>8} {'F1':>8} {'TC':>8}") - print("-" * 50) - for tool in tools: - entries = [r for r in cat_groups.get(tool, []) if "error" not in r] - if not entries: - continue - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - print(f"{tool:>12} {rec:>8.3f} {prec:>8.3f} {f1:>8.3f} {tc:>8.3f}") - - out = Path("benchmarks/data/external_balibase.json") - out.parent.mkdir(parents=True, exist_ok=True) - json.dump(results, open(out, "w"), indent=2) - print(f"\nSaved to {out}") - - -if __name__ == "__main__": - main() diff --git a/benchmarks/full_comparison.py b/benchmarks/full_comparison.py deleted file mode 100644 index cd92fed..0000000 --- a/benchmarks/full_comparison.py +++ /dev/null @@ -1,214 +0,0 @@ -"""Full BAliBASE comparison: all kalign modes + external tools (cached). - -Shows ALL metrics: SP(=Recall), Precision, F1, TC, Time — overall and per-category. -""" - -import json -import statistics -import tempfile -import time -from collections import defaultdict -from concurrent.futures import ProcessPoolExecutor, as_completed -from pathlib import Path - -import kalign -from benchmarks.datasets import get_cases -from benchmarks.scoring import parse_balibase_xml - - -def _score_case(case, output_path): - xml_path = case.reference.with_suffix(".xml") - if xml_path.exists(): - mask = parse_balibase_xml(xml_path) - return kalign.compare_detailed( - str(case.reference), str(output_path), column_mask=mask - ) - return kalign.compare_detailed(str(case.reference), str(output_path)) - - -def _run_one(args): - case, config_name, kwargs = args - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - t0 = time.perf_counter() - kalign.align_file_to_file( - str(case.unaligned), - tmp_path, - format="fasta", - seq_type=case.seq_type, - **kwargs, - ) - wall_time = time.perf_counter() - t0 - scores = _score_case(case, tmp_path) - return { - "family": case.family, - "dataset": case.dataset, - "config": config_name, - **scores, - "wall_time": wall_time, - } - except Exception as e: - return { - "family": case.family, - "dataset": case.dataset, - "config": config_name, - "error": str(e), - } - finally: - Path(tmp_path).unlink(missing_ok=True) - - -def _print_table(title, display_order, summary_data, configs): - """Print one table with ALL metrics: SP, Prec, F1, TC, Time.""" - print(f"\n{'=' * 100}") - print(f" {title}") - print(f"{'=' * 100}") - print( - f"{'Method':<24} {'SP':>8} {'Prec':>8} {'F1':>8} {'TC':>8}" - f" {'Time':>8} {'N':>4}" - ) - print("-" * 100) - for name in display_order: - if name not in summary_data: - continue - sp, prec, f1, tc, t, n = summary_data[name] - marker = " " if name in configs else "*" - t_str = f"{t:.0f}s" if t is not None else "—" - print( - f"{marker}{name:<23} {sp:>8.3f} {prec:>8.3f} {f1:>8.3f}" - f" {tc:>8.3f} {t_str:>8} {n:>4}" - ) - print("-" * 100) - print(" SP = Sum-of-Pairs recall (BAliBASE convention)") - print(" * = external tool (cached results)") - - -def main(): - cases = get_cases("balibase") - print(f"BAliBASE: {len(cases)} cases\n") - - # Kalign configurations to test - configs = { - "baseline(vsm=0)": {"vsm_amax": 0.0}, - "+vsm": {"vsm_amax": 2.0}, - "+vsm+ref": {"vsm_amax": 2.0, "refine": "confident"}, - "+vsm+ref+sw": { - "vsm_amax": 2.0, - "refine": "confident", - "seq_weights": 1.0, - }, - "+vsm+ref+sw+c5": { - "vsm_amax": 2.0, - "refine": "confident", - "seq_weights": 1.0, - "consistency": 5, - "consistency_weight": 2.0, - }, - "ens3+vsm": { - "vsm_amax": 2.0, - "ensemble": 3, - }, - "ens3+vsm+ref": { - "vsm_amax": 2.0, - "ensemble": 3, - "refine": "confident", - }, - "ens3+vsm+ref+ra1": { - "vsm_amax": 2.0, - "ensemble": 3, - "refine": "confident", - "realign": 1, - }, - "ens3+vsm+ref+ra1+c5": { - "vsm_amax": 2.0, - "ensemble": 3, - "refine": "confident", - "realign": 1, - "consistency": 5, - "consistency_weight": 2.0, - }, - } - - tasks = [(c, name, kw) for c in cases for name, kw in configs.items()] - print(f"{len(configs)} configs x {len(cases)} cases = {len(tasks)} tasks\n") - - results = [] - done = 0 - t0 = time.perf_counter() - with ProcessPoolExecutor(max_workers=12) as pool: - futures = {pool.submit(_run_one, t): t for t in tasks} - for f in as_completed(futures): - done += 1 - r = f.result() - results.append(r) - if done % 200 == 0: - elapsed = time.perf_counter() - t0 - print(f" {done}/{len(tasks)} ({elapsed:.0f}s)") - - elapsed = time.perf_counter() - t0 - print(f"\nKalign runs done in {elapsed:.0f}s\n") - - # Load cached external results - ext_path = Path("benchmarks/data/external_balibase.json") - if ext_path.exists(): - ext_data = json.load(open(ext_path)) - for r in ext_data: - r["config"] = r.pop("method") - results.extend(ext_data) - ext_methods = set(r["config"] for r in ext_data) - print(f"Loaded {len(ext_data)} cached external results: {ext_methods}\n") - - # Group by config - groups = defaultdict(list) - for r in results: - if "error" not in r: - groups[r["config"]].append(r) - - # Display order: kalign configs first, then external sorted - display_order = list(configs.keys()) + sorted( - k for k in groups if k not in configs - ) - - # Compute overall summary: (sp, prec, f1, tc, total_time, count) - overall = {} - for name in display_order: - entries = groups.get(name, []) - if not entries: - continue - sp = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - t = sum(r["wall_time"] for r in entries) - overall[name] = (sp, prec, f1, tc, t, len(entries)) - - # Print overall table - _print_table("OVERALL (218 cases)", display_order, overall, configs) - - # Group results by category - categories = ["RV11", "RV12", "RV20", "RV30", "RV40", "RV50"] - by_cat = defaultdict(lambda: defaultdict(list)) - for r in results: - if "error" not in r: - cat = r["dataset"].replace("balibase_", "") - by_cat[cat][r["config"]].append(r) - - # Per-category tables — each with ALL metrics - for cat in categories: - cat_summary = {} - for name in display_order: - entries = by_cat[cat].get(name, []) - if not entries: - continue - sp = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - t = sum(r["wall_time"] for r in entries) - cat_summary[name] = (sp, prec, f1, tc, t, len(entries)) - _print_table(f"{cat} ({len(by_cat[cat].get(display_order[0], []))} cases)", display_order, cat_summary, configs) - - -if __name__ == "__main__": - main() diff --git a/benchmarks/make_summary_figure.py b/benchmarks/make_summary_figure.py deleted file mode 100644 index 99d9d80..0000000 --- a/benchmarks/make_summary_figure.py +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env python3 -"""Generate a summary figure from downstream benchmark results. - -Produces a 2x2 grid of line plots showing method performance vs difficulty, -with the "true" alignment as a dashed black ceiling line. - -- Panel A: Phylo accuracy (nRF) vs tree_depth -- Panel B: Positive selection (F1) vs n_taxa -- Panel C: Alignment accuracy (SP) vs tree_depth -- Panel D: Alignment accuracy (SP) vs indel_rate -""" - -import json -import re -from collections import defaultdict -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np - -RESULTS_DIR = Path("benchmarks/results") - -# Method display order (true last — rendered as dashed black) -METHOD_ORDER = [ - "kalign", "kalign_ens3", - "mafft", "muscle", "clustalo", "true", -] -METHOD_LABELS = { - "kalign": "Kalign", - "kalign_ens3": "Kalign ens3", - "mafft": "MAFFT", - "muscle": "MUSCLE", - "clustalo": "Clustal Omega", - "true": "True alignment", -} -METHOD_COLORS = { - "kalign": "#1f77b4", - "kalign_ens3": "#2ca02c", - "mafft": "#9467bd", - "muscle": "#8c564b", - "clustalo": "#7f7f7f", - "true": "#000000", -} - - -def load_cases(pipeline: str) -> list[dict]: - """Load per-case results from the latest.json for a pipeline.""" - path = RESULTS_DIR / pipeline / "latest.json" - with open(path) as f: - data = json.load(f) - return data.get("cases", []) - - -def parse_sim_id(sim_id: str) -> dict: - """Extract parameters from a sim_id string. - - Examples: - WAG_t16_d2.0_ir0.10_il2.0_r0 -> {model:WAG, n_taxa:16, tree_depth:2.0, ...} - M8_t16_d0.5_ir0.05_ps0.10_r0 -> {model:M8, n_taxa:16, tree_depth:0.5, ...} - """ - params = {} - m = re.match(r"^(\w+)_t(\d+)_d([\d.]+)_ir([\d.]+)", sim_id) - if m: - params["model"] = m.group(1) - params["n_taxa"] = int(m.group(2)) - params["tree_depth"] = float(m.group(3)) - params["indel_rate"] = float(m.group(4)) - - # Protein: _il{mean}_r{rep} - m_il = re.search(r"_il([\d.]+)", sim_id) - if m_il: - params["indel_length_mean"] = float(m_il.group(1)) - - # Codon: _ps{frac}_r{rep} - m_ps = re.search(r"_ps([\d.]+)", sim_id) - if m_ps: - params["psel_fraction"] = float(m_ps.group(1)) - - m_r = re.search(r"_r(\d+)$", sim_id) - if m_r: - params["replicate"] = int(m_r.group(1)) - - return params - - -def group_by(cases: list[dict], param_key: str, metric_key: str): - """Group per-case results by a sim_id parameter. - - Returns {method: {x_value: [metric_values]}}. - """ - grouped = defaultdict(lambda: defaultdict(list)) - for c in cases: - if "error" in c: - continue - sim_params = parse_sim_id(c.get("sim_id", "")) - if param_key not in sim_params: - continue - x_val = sim_params[param_key] - method = c["method"] - val = c.get(metric_key) - if val is not None and not (isinstance(val, float) and np.isnan(val)): - grouped[method][x_val].append(val) - return grouped - - -def style_ax(ax, hint=""): - """Apply clean Nature-style aesthetics.""" - ax.spines["top"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.tick_params(labelsize=9) - if hint: - ax.annotate( - hint, xy=(0.98, 0.95), xycoords="axes fraction", - ha="right", va="top", fontsize=8, fontstyle="italic", color="gray", - ) - - -def plot_lines(ax, grouped, methods, xlabel, ylabel): - """Plot lines with error ribbons for each method. - - Parameters - ---------- - grouped : dict - {method: {x_value: [metric_values]}} - methods : list[str] - Method names in plot order. - """ - for method in methods: - if method not in grouped: - continue - data = grouped[method] - xs = sorted(data.keys()) - means = [] - ses = [] - for x in xs: - vals = np.asarray(data[x], dtype=float) - means.append(vals.mean()) - ses.append(vals.std() / max(1, np.sqrt(len(vals)))) - - means = np.asarray(means) - ses = np.asarray(ses) - - color = METHOD_COLORS.get(method, "#333333") - label = METHOD_LABELS.get(method, method) - linestyle = "--" if method == "true" else "-" - linewidth = 1.5 if method != "true" else 2.0 - marker = "o" if method != "true" else "" - - ax.plot( - xs, means, color=color, linestyle=linestyle, - linewidth=linewidth, marker=marker, markersize=4, - label=label, zorder=3 if method != "true" else 2, - ) - ax.fill_between( - xs, means - ses, means + ses, - color=color, alpha=0.12, zorder=1, - ) - - ax.set_xlabel(xlabel, fontsize=10) - ax.set_ylabel(ylabel, fontsize=10) - - -def main(): - fig, axes = plt.subplots(2, 2, figsize=(14, 10)) - fig.suptitle( - "Kalign 3.5 — Downstream Benchmarks by Difficulty", - fontsize=16, fontweight="bold", y=0.98, - ) - - # Determine which methods are present in results - available = set() - for pipeline in ("phylo_accuracy", "positive_selection", "calibration"): - try: - cases = load_cases(pipeline) - for c in cases: - if "error" not in c: - available.add(c["method"]) - except FileNotFoundError: - pass - methods = [m for m in METHOD_ORDER if m in available] - - # ── Panel A: Phylo accuracy (nRF) vs tree_depth ────────────────── - ax = axes[0, 0] - try: - cases = load_cases("phylo_accuracy") - grouped = group_by(cases, "tree_depth", "nrf") - plot_lines(ax, grouped, methods, "Tree depth", "Normalized RF distance") - style_ax(ax, "lower = better") - ax.set_title("A) Phylogenetic tree accuracy", fontweight="bold", loc="left", fontsize=11) - except FileNotFoundError: - ax.text(0.5, 0.5, "No phylo_accuracy results", transform=ax.transAxes, ha="center") - style_ax(ax) - - # ── Panel B: Positive selection (F1) vs n_taxa ─────────────────── - ax = axes[0, 1] - try: - cases = load_cases("positive_selection") - grouped = group_by(cases, "n_taxa", "f1") - plot_lines(ax, grouped, methods, "Number of taxa", "F1 score") - style_ax(ax, "higher = better") - ax.set_title("B) Positive selection detection (FUBAR)", fontweight="bold", loc="left", fontsize=11) - except FileNotFoundError: - ax.text(0.5, 0.5, "No positive_selection results", transform=ax.transAxes, ha="center") - style_ax(ax) - - # ── Panel C: Alignment accuracy (SP) vs tree_depth ─────────────── - ax = axes[1, 0] - try: - cases = load_cases("phylo_accuracy") - grouped = group_by(cases, "tree_depth", "sp_score") - plot_lines(ax, grouped, methods, "Tree depth", "SP score vs true alignment") - style_ax(ax, "higher = better") - ax.set_title("C) Alignment accuracy vs tree depth", fontweight="bold", loc="left", fontsize=11) - except FileNotFoundError: - ax.text(0.5, 0.5, "No phylo_accuracy results", transform=ax.transAxes, ha="center") - style_ax(ax) - - # ── Panel D: Alignment accuracy (SP) vs indel_rate ─────────────── - ax = axes[1, 1] - try: - cases = load_cases("phylo_accuracy") - grouped = group_by(cases, "indel_rate", "sp_score") - plot_lines(ax, grouped, methods, "Indel rate", "SP score vs true alignment") - style_ax(ax, "higher = better") - ax.set_title("D) Alignment accuracy vs indel rate", fontweight="bold", loc="left", fontsize=11) - except FileNotFoundError: - ax.text(0.5, 0.5, "No phylo_accuracy results", transform=ax.transAxes, ha="center") - style_ax(ax) - - # Add legend (shared across panels) - handles, labels = axes[0, 0].get_legend_handles_labels() - if handles: - fig.legend( - handles, labels, loc="lower center", - ncol=min(len(handles), 7), fontsize=9, - frameon=False, bbox_to_anchor=(0.5, 0.0), - ) - - plt.tight_layout(rect=[0, 0.04, 1, 0.95]) - out = Path("benchmarks/figures/downstream_summary.png") - out.parent.mkdir(parents=True, exist_ok=True) - fig.savefig(out, dpi=200, bbox_inches="tight", facecolor="white") - print(f"Saved to {out}") - plt.close() - - -if __name__ == "__main__": - main() diff --git a/benchmarks/mumsa_plots.py b/benchmarks/mumsa_plots.py deleted file mode 100644 index 02c531e..0000000 --- a/benchmarks/mumsa_plots.py +++ /dev/null @@ -1,220 +0,0 @@ -"""Generate publication figures for MUMSA consensus precision analysis. - -Reads results from benchmarks/results/mumsa_precision.json (produced by -mumsa_precision.py) and generates two figures: - - Figure 1: Precision-recall tradeoff curve showing consensus support - thresholds vs single-tool baselines. - Figure 2: Per-category precision bar chart at selected thresholds - alongside external tools. - -Usage: - python -m benchmarks.mumsa_plots [--output-dir figures/] - -Only requires matplotlib + json (no kalign dependency). -""" - -import argparse -import json -import statistics -from collections import defaultdict -from pathlib import Path - -import matplotlib -matplotlib.use("Agg") -import matplotlib.pyplot as plt - - -# Consistent colours -COLORS = { - "consensus": "#2166ac", - "kalign_baseline": "#888888", - "ensemble": "#4daf4a", - "mafft": "#e41a1c", - "muscle": "#ff7f00", - "clustalo": "#984ea3", -} - -TOOL_LABELS = { - "kalign_baseline": "kalign", - "ensemble": "kalign ensemble", - "mafft": "MAFFT", - "muscle": "MUSCLE", - "clustalo": "Clustal Omega", -} - - -def load_results(json_path): - data = json.load(open(json_path)) - return data["results"], data.get("n_runs", 8) - - -def aggregate(results, group_key="method"): - """Group results by method, return {method: {metric: mean_value}}.""" - groups = defaultdict(list) - for r in results: - groups[r[group_key]].append(r) - agg = {} - for method, entries in groups.items(): - agg[method] = { - "recall": statistics.mean(e["recall"] for e in entries), - "precision": statistics.mean(e["precision"] for e in entries), - "f1": statistics.mean(e["f1"] for e in entries), - "tc": statistics.mean(e["tc"] for e in entries), - "n": len(entries), - } - return agg - - -def aggregate_by_category(results): - """Group results by (category, method).""" - groups = defaultdict(list) - for r in results: - cat = r["dataset"].replace("balibase_", "") - groups[(cat, r["method"])].append(r) - agg = {} - for (cat, method), entries in groups.items(): - agg[(cat, method)] = { - "recall": statistics.mean(e["recall"] for e in entries), - "precision": statistics.mean(e["precision"] for e in entries), - "f1": statistics.mean(e["f1"] for e in entries), - "tc": statistics.mean(e["tc"] for e in entries), - "n": len(entries), - } - return agg - - -def figure_precision_vs_support(results, n_runs, output_path): - """Precision and recall as a function of consensus support threshold, - with horizontal reference lines for external tools and kalign baseline.""" - agg = aggregate(results) - - fig, ax = plt.subplots(figsize=(6, 4.5)) - - # Consensus curves - consensus_methods = sorted( - [m for m in agg if m.startswith("consensus_ms")], - key=lambda m: int(m.replace("consensus_ms", "")) - ) - support_vals = [int(m.replace("consensus_ms", "")) for m in consensus_methods] - prec = [agg[m]["precision"] for m in consensus_methods] - rec = [agg[m]["recall"] for m in consensus_methods] - - ax.plot(support_vals, prec, "o-", color=COLORS["consensus"], linewidth=2.5, - markersize=7, zorder=5, label="Consensus precision") - ax.plot(support_vals, rec, "s--", color=COLORS["consensus"], linewidth=1.5, - markersize=5, alpha=0.5, zorder=4, label="Consensus recall") - - # Reference lines for external tools and baselines (precision only) - ref_methods = [ - ("kalign_baseline", COLORS["kalign_baseline"], "kalign"), - ("ensemble", COLORS["ensemble"], "kalign ensemble"), - ("mafft", COLORS["mafft"], "MAFFT"), - ("muscle", COLORS["muscle"], "MUSCLE"), - ("clustalo", COLORS["clustalo"], "Clustal Omega"), - ] - for method, color, label in ref_methods: - if method in agg: - ax.axhline(agg[method]["precision"], color=color, linestyle=":", - linewidth=1.5, alpha=0.8, label=f"{label} precision") - - ax.set_xlabel("Minimum support threshold (s)", fontsize=11) - ax.set_ylabel("Score", fontsize=11) - ax.set_title("Consensus precision increases with support threshold", fontsize=11) - ax.set_xticks(support_vals) - ax.legend(fontsize=7.5, loc="center left", bbox_to_anchor=(1.02, 0.5), - framealpha=0.9) - ax.set_ylim(0.35, 1.0) - ax.grid(True, alpha=0.3) - - fig.tight_layout() - fig.savefig(output_path, dpi=300, bbox_inches="tight") - plt.close(fig) - print(f"Saved {output_path}") - - -def figure_precision_by_category(results, n_runs, output_path): - """Per-category grouped bar chart of precision at selected thresholds.""" - agg = aggregate_by_category(results) - - categories = sorted({r["dataset"].replace("balibase_", "") for r in results}) - - # Methods to show: baseline, ms=3, ms=n_runs, ensemble, mafft, muscle, clustalo - mid = max(1, n_runs // 2) - bar_methods = [ - ("kalign_baseline", "kalign", COLORS["kalign_baseline"]), - (f"consensus_ms{mid}", f"consensus s={mid}", "#6baed6"), - (f"consensus_ms{n_runs}", f"consensus s={n_runs}", COLORS["consensus"]), - ("ensemble", "ensemble", COLORS["ensemble"]), - ("mafft", "MAFFT", COLORS["mafft"]), - ("muscle", "MUSCLE", COLORS["muscle"]), - ("clustalo", "Clustal\u03a9", COLORS["clustalo"]), - ] - - n_cats = len(categories) - n_bars = len(bar_methods) - bar_width = 0.11 - x = range(n_cats) - - fig, ax = plt.subplots(figsize=(9, 4.5)) - - for bi, (method, label, color) in enumerate(bar_methods): - vals = [] - for cat in categories: - key = (cat, method) - vals.append(agg[key]["precision"] if key in agg else 0) - offsets = [xi + (bi - n_bars / 2 + 0.5) * bar_width for xi in x] - ax.bar(offsets, vals, bar_width, label=label, color=color, - edgecolor="white", linewidth=0.3) - - ax.set_xlabel("BAliBASE category", fontsize=11) - ax.set_ylabel("Precision", fontsize=11) - ax.set_title("Precision by category and method", fontsize=11) - ax.set_xticks(list(x)) - ax.set_xticklabels(categories, fontsize=10) - ax.legend(fontsize=7.5, ncol=4, loc="upper center", - bbox_to_anchor=(0.5, -0.12), framealpha=0.9) - ax.set_ylim(0, 1.05) - ax.grid(True, axis="y", alpha=0.3) - - fig.tight_layout() - fig.savefig(output_path, dpi=300, bbox_inches="tight") - plt.close(fig) - print(f"Saved {output_path}") - - -def main(): - parser = argparse.ArgumentParser( - description="Generate MUMSA precision figures from saved results." - ) - parser.add_argument( - "--input", default="benchmarks/results/mumsa_precision.json", - help="Path to results JSON (default: benchmarks/results/mumsa_precision.json)" - ) - parser.add_argument( - "--output-dir", default="benchmarks/figures", - help="Directory for output figures (default: benchmarks/figures/)" - ) - parser.add_argument( - "--format", default="pdf", choices=["pdf", "png", "svg"], - help="Output format (default: pdf)" - ) - args = parser.parse_args() - - results, n_runs = load_results(args.input) - - out_dir = Path(args.output_dir) - out_dir.mkdir(parents=True, exist_ok=True) - - figure_precision_vs_support( - results, n_runs, - out_dir / f"mumsa_precision_vs_support.{args.format}", - ) - figure_precision_by_category( - results, n_runs, - out_dir / f"mumsa_precision_by_category.{args.format}", - ) - - -if __name__ == "__main__": - main() diff --git a/benchmarks/mumsa_precision.py b/benchmarks/mumsa_precision.py deleted file mode 100644 index ea24974..0000000 --- a/benchmarks/mumsa_precision.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Verify MUMSA claim: consensus alignments at higher support thresholds -have higher precision (fraction of aligned residue pairs that are correct). - -Runs kalign ensemble_consensus at min_support = 1..N for each BAliBASE case, -scores against references, and compares precision with baseline/external tools. -""" - -import argparse -import json -import statistics -import tempfile -import time -from collections import defaultdict -from concurrent.futures import ProcessPoolExecutor, as_completed -from pathlib import Path - -import kalign -from .datasets import get_cases -from .scoring import parse_balibase_xml - - -def score_case(case, output_path): - """Score a test alignment against the BAliBASE reference. - Returns dict with recall, precision, f1, tc.""" - xml_path = case.reference.with_suffix(".xml") - if xml_path.exists(): - mask = parse_balibase_xml(xml_path) - return kalign.compare_detailed( - str(case.reference), str(output_path), column_mask=mask - ) - return kalign.compare_detailed(str(case.reference), str(output_path)) - - -def _run_one_case(case, n_runs, max_support): - """Process a single BAliBASE case: run all support thresholds + baseline + ensemble. - Returns list of result dicts.""" - results = [] - - for ms in range(1, max_support + 1): - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - kalign.align_file_to_file( - str(case.unaligned), tmp_path, format="fasta", - seq_type=case.seq_type, ensemble=n_runs, min_support=ms, - ) - scores = score_case(case, tmp_path) - results.append({ - "family": case.family, "dataset": case.dataset, - "method": f"consensus_ms{ms}", "min_support": ms, - "n_runs": n_runs, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - }) - except Exception as e: - results.append({ - "family": case.family, "dataset": case.dataset, - "method": f"consensus_ms{ms}", "min_support": ms, - "n_runs": n_runs, - "recall": 0, "precision": 0, "f1": 0, "tc": 0, - "error": str(e), - }) - finally: - Path(tmp_path).unlink(missing_ok=True) - - # Normal ensemble (no consensus) - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - kalign.align_file_to_file( - str(case.unaligned), tmp_path, format="fasta", - seq_type=case.seq_type, ensemble=n_runs, - ) - scores = score_case(case, tmp_path) - results.append({ - "family": case.family, "dataset": case.dataset, - "method": "ensemble", "min_support": 0, "n_runs": n_runs, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - }) - except Exception: - pass - finally: - Path(tmp_path).unlink(missing_ok=True) - - # Baseline kalign - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - kalign.align_file_to_file( - str(case.unaligned), tmp_path, format="fasta", - seq_type=case.seq_type, - ) - scores = score_case(case, tmp_path) - results.append({ - "family": case.family, "dataset": case.dataset, - "method": "kalign_baseline", "min_support": 0, "n_runs": 0, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - }) - except Exception: - pass - finally: - Path(tmp_path).unlink(missing_ok=True) - - return results - - -def _worker(args): - """Picklable worker for ProcessPoolExecutor.""" - case, n_runs, max_support = args - return _run_one_case(case, n_runs, max_support) - - -def run_consensus_sweep(cases, n_runs=8, max_support=None, categories=None, parallel=1): - """Run ensemble consensus at each support threshold for each case.""" - if max_support is None: - max_support = n_runs - - # Filter cases - filtered = [] - for case in cases: - if categories and not any(cat in case.dataset.upper() for cat in categories): - continue - filtered.append(case) - - all_results = [] - - if parallel > 1: - tasks = [(case, n_runs, max_support) for case in filtered] - done = 0 - with ProcessPoolExecutor(max_workers=parallel) as pool: - futures = {pool.submit(_worker, t): t[0].family for t in tasks} - for future in as_completed(futures): - done += 1 - fam = futures[future] - try: - results = future.result() - all_results.extend(results) - print(f" [{done}/{len(filtered)}] {fam} done") - except Exception as e: - print(f" [{done}/{len(filtered)}] {fam} FAILED: {e}") - else: - for ci, case in enumerate(filtered): - print(f" [{ci+1}/{len(filtered)}] {case.family} ({case.dataset})", end="", flush=True) - results = _run_one_case(case, n_runs, max_support) - all_results.extend(results) - print(" done") - - return all_results - - -def load_external_scores(json_path="benchmarks/results/full_comparison.json"): - """Load external tool scores (mafft, muscle, clustalo) from saved results.""" - p = Path(json_path) - if not p.exists(): - return {} - data = json.load(open(p)) - ext = {} - for r in data["results"]: - if r["method"] in ("mafft", "muscle", "clustalo"): - key = (r["family"], r["method"]) - ext[key] = { - "recall": r.get("recall", 0), - "precision": r.get("precision", 0), - "f1": r.get("f1", 0), - "tc": r.get("tc", 0), - } - return ext - - -def summarize(results, external_scores=None): - """Print summary table of precision across support thresholds.""" - by_method = defaultdict(list) - by_method_cat = defaultdict(lambda: defaultdict(list)) - - for r in results: - by_method[r["method"]].append(r) - cat = r["dataset"].replace("balibase_", "") - by_method_cat[r["method"]][cat].append(r) - - if external_scores: - families = {r["family"] for r in results} - family_datasets = {r["family"]: r["dataset"] for r in results} - for tool in ("mafft", "muscle", "clustalo"): - for fam in families: - key = (fam, tool) - if key in external_scores: - s = external_scores[key] - ds = family_datasets.get(fam, "") - cat = ds.replace("balibase_", "") - entry = {"family": fam, "dataset": ds, **s} - by_method[tool].append(entry) - by_method_cat[tool][cat].append(entry) - - def method_sort_key(m): - if m == "kalign_baseline": - return (0, 0) - if m.startswith("consensus_ms"): - return (1, int(m.replace("consensus_ms", ""))) - if m == "ensemble": - return (2, 0) - return (3, {"mafft": 0, "muscle": 1, "clustalo": 2}.get(m, 9)) - - methods = sorted(by_method.keys(), key=method_sort_key) - - print("\n=== Overall (all categories) ===") - print(f"{'Method':<22} {'Recall':>8} {'Precision':>10} {'F1':>8} {'TC':>8} N") - print("-" * 70) - for m in methods: - entries = by_method[m] - n = len(entries) - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - print(f"{m:<22} {rec:>8.3f} {prec:>10.3f} {f1:>8.3f} {tc:>8.3f} {n}") - - all_cats = sorted({r["dataset"].replace("balibase_", "") for r in results}) - for cat in all_cats: - print(f"\n=== {cat} ===") - print(f"{'Method':<22} {'Recall':>8} {'Precision':>10} {'F1':>8} {'TC':>8} N") - print("-" * 70) - for m in methods: - entries = by_method_cat[m].get(cat, []) - if not entries: - continue - n = len(entries) - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - print(f"{m:<22} {rec:>8.3f} {prec:>10.3f} {f1:>8.3f} {tc:>8.3f} {n}") - - -def main(): - parser = argparse.ArgumentParser(description="MUMSA precision verification") - parser.add_argument("--n-runs", type=int, default=8, - help="Number of ensemble runs (default: 8)") - parser.add_argument("--max-support", type=int, default=None, - help="Maximum min_support threshold (default: n_runs)") - parser.add_argument("--categories", nargs="*", default=None, - help="BAliBASE categories to test (e.g. RV11 RV12)") - parser.add_argument("--max-cases", type=int, default=0, - help="Limit number of cases (0 = all)") - parser.add_argument("--parallel", "-j", type=int, default=4, - help="Number of parallel workers (default: 4)") - args = parser.parse_args() - - categories = [c.upper() for c in args.categories] if args.categories else None - - cases = get_cases("balibase", max_cases=args.max_cases if args.max_cases else None) - print(f"Running MUMSA precision analysis on {len(cases)} BAliBASE cases") - print(f"Ensemble runs: {args.n_runs}, max support: {args.max_support or args.n_runs}") - print(f"Parallel workers: {args.parallel}") - - results = run_consensus_sweep( - cases, n_runs=args.n_runs, - max_support=args.max_support, - categories=categories, - parallel=args.parallel, - ) - - external = load_external_scores() - summarize(results, external) - - # Save results for plotting - out_dir = Path("benchmarks/results") - out_dir.mkdir(parents=True, exist_ok=True) - out_path = out_dir / "mumsa_precision.json" - # Merge external scores into a flat list alongside our results - ext_list = [] - if external: - families = {r["family"] for r in results} - family_datasets = {r["family"]: r["dataset"] for r in results} - for tool in ("mafft", "muscle", "clustalo"): - for fam in families: - key = (fam, tool) - if key in external: - ext_list.append({ - "family": fam, "dataset": family_datasets.get(fam, ""), - "method": tool, "min_support": 0, "n_runs": 0, - **external[key], - }) - json.dump({"results": results + ext_list, "n_runs": args.n_runs}, - open(out_path, "w"), indent=2) - print(f"\nResults saved to {out_path}") - - -if __name__ == "__main__": - main() diff --git a/benchmarks/runner.py b/benchmarks/runner.py index ad3cb9a..a9526dc 100644 --- a/benchmarks/runner.py +++ b/benchmarks/runner.py @@ -14,40 +14,33 @@ def _run_one(args): """Worker function for parallel execution.""" - case, method, binary, n_threads, refine, adaptive_budget, ensemble = args + case, method, binary, n_threads, mode = args return run_case(case, method=method, binary=binary, n_threads=n_threads, - refine=refine, adaptive_budget=adaptive_budget, ensemble=ensemble) + mode=mode) def _result_label(r) -> str: - """Format a concise label showing method and config for verbose output.""" + """Format a concise label for verbose output.""" if r.method in EXTERNAL_TOOLS: return r.method - parts = ["kalign"] - if r.refine != "none": - parts.append(f"refine={r.refine}") - if r.ensemble: - parts.append(f"ens={r.ensemble}") - return " ".join(parts) + return f"kalign {r.refine}" def run_benchmark( dataset: str = "balibase", methods: Optional[List[str]] = None, - refine_modes: Optional[List[str]] = None, + modes: Optional[List[str]] = None, max_cases: int = 0, binary: str = "kalign", n_threads: int = 1, verbose: bool = False, - adaptive_budget: bool = False, - ensemble: int = 0, parallel: int = 1, ) -> List[AlignmentResult]: """Run benchmark suite and return results.""" if methods is None: - methods = ["python_api"] - if refine_modes is None: - refine_modes = ["none"] + methods = ["cli"] + if modes is None: + modes = ["default"] cases = get_cases(dataset, max_cases=max_cases if max_cases > 0 else None) @@ -56,9 +49,11 @@ def run_benchmark( print("Try running with --download-only first.") return [] - print(f"Running {len(cases)} cases from '{dataset}' with methods: {methods}, refine: {refine_modes}") + print(f"Running {len(cases)} cases from '{dataset}'") + print(f" Methods: {methods}") + print(f" Modes: {modes}") if parallel > 1: - print(f"Using {parallel} parallel workers") + print(f" Workers: {parallel}") print() # Build work items @@ -66,16 +61,14 @@ def run_benchmark( for case in cases: for method in methods: if method in EXTERNAL_TOOLS: - # External tools don't support refine/ensemble — run once - work.append((case, method, binary, n_threads, "none", False, 0)) + work.append((case, method, binary, n_threads, "default")) else: - for refine in refine_modes: - work.append((case, method, binary, n_threads, refine, adaptive_budget, ensemble)) + for mode in modes: + work.append((case, method, binary, n_threads, mode)) total = len(work) if parallel <= 1: - # Sequential (original behavior) results = [] for i, item in enumerate(work): result = _run_one(item) @@ -87,15 +80,15 @@ def run_benchmark( else: print(f"[{i+1}/{total}] {result.family:<12} {label:<25} SP={result.recall:.3f} TC={result.tc:.3f} F1={result.f1:.3f} {result.wall_time:.1f}s") else: - # Parallel execution - results = [None] * total + results = [] done = 0 with ProcessPoolExecutor(max_workers=parallel) as pool: futures = {pool.submit(_run_one, item): i for i, item in enumerate(work)} + indexed_results = [None] * total for future in as_completed(futures): idx = futures[future] result = future.result() - results[idx] = result + indexed_results[idx] = result done += 1 if verbose: label = _result_label(result) @@ -103,6 +96,7 @@ def run_benchmark( print(f"[{done}/{total}] {result.family:<12} {label:<25} ERROR: {result.error}") else: print(f"[{done}/{total}] {result.family:<12} {label:<25} SP={result.recall:.3f} TC={result.tc:.3f} F1={result.f1:.3f} {result.wall_time:.1f}s") + results = [r for r in indexed_results if r is not None] return results @@ -113,36 +107,54 @@ def print_summary(results: List[AlignmentResult]) -> None: for r in results: if r.error: continue - ens = f" ensemble={r.ensemble}" if r.ensemble else "" - key = f"{r.method} refine={r.refine}{ens}" + if r.method in EXTERNAL_TOOLS: + key = r.method + else: + key = f"kalign {r.refine}" by_group.setdefault(key, []).append(r) + print(f"\n{'Method':<24} {'SP':>8} {'Prec':>8} {'F1':>8} {'TC':>8} {'Time':>8} {'N':>5}") + print("-" * 75) + for group, group_results in sorted(by_group.items()): recalls = [r.recall for r in group_results] precisions = [r.precision for r in group_results] f1s = [r.f1 for r in group_results] tcs = [r.tc for r in group_results] - times = [r.wall_time for r in group_results] - - print(f"\n--- {group} ({len(group_results)} cases) ---") - print(f" SP: mean={statistics.mean(recalls):.3f} " - f"median={statistics.median(recalls):.3f} " - f"min={min(recalls):.3f} max={max(recalls):.3f}") - print(f" TC: mean={statistics.mean(tcs):.3f} " - f"median={statistics.median(tcs):.3f}") - print(f" Precision: mean={statistics.mean(precisions):.3f} " - f"median={statistics.median(precisions):.3f}") - print(f" F1: mean={statistics.mean(f1s):.3f} " - f"median={statistics.median(f1s):.3f}") - print(f" Time (s): total={sum(times):.1f} " - f"mean={statistics.mean(times):.2f} " - f"max={max(times):.2f}") + total_time = sum(r.wall_time for r in group_results) + + print(f"{group:<24} {statistics.mean(recalls):>8.3f} {statistics.mean(precisions):>8.3f} " + f"{statistics.mean(f1s):>8.3f} {statistics.mean(tcs):>8.3f} " + f"{total_time:>7.0f}s {len(group_results):>5}") + + # Per-category breakdown + categories = sorted({r.dataset for r in results if not r.error}) + if len(categories) > 1: + for cat in categories: + cat_results = [r for r in results if r.dataset == cat and not r.error] + if not cat_results: + continue + cat_groups = {} + for r in cat_results: + key = r.method if r.method in EXTERNAL_TOOLS else f"kalign {r.refine}" + cat_groups.setdefault(key, []).append(r) + + cat_name = cat.replace("balibase_", "") + n = len(next(iter(cat_groups.values()))) + print(f"\n--- {cat_name} ({n} cases) ---") + print(f"{'Method':<24} {'SP':>8} {'Prec':>8} {'F1':>8} {'TC':>8}") + print("-" * 60) + for group, gr in sorted(cat_groups.items()): + print(f"{group:<24} {statistics.mean(r.recall for r in gr):>8.3f} " + f"{statistics.mean(r.precision for r in gr):>8.3f} " + f"{statistics.mean(r.f1 for r in gr):>8.3f} " + f"{statistics.mean(r.tc for r in gr):>8.3f}") errors = [r for r in results if r.error] if errors: print(f"\n{len(errors)} error(s):") for r in errors: - print(f" {r.family} ({r.method} refine={r.refine}): {r.error}") + print(f" {r.family} ({r.method}): {r.error}") def save_results(results: List[AlignmentResult], path: str) -> None: @@ -157,22 +169,16 @@ def save_results(results: List[AlignmentResult], path: str) -> None: for r in results: if r.error: continue - ens = f"_ensemble={r.ensemble}" if r.ensemble else "" - key = f"{r.method}_refine={r.refine}{ens}" + key = r.method if r.method in EXTERNAL_TOOLS else f"kalign_{r.refine}" by_group.setdefault(key, []).append(r) for group, group_results in by_group.items(): - scores = [r.sp_score for r in group_results] recalls = [r.recall for r in group_results] precisions = [r.precision for r in group_results] f1s = [r.f1 for r in group_results] tcs = [r.tc for r in group_results] data["summary"][group] = { "n_cases": len(group_results), - "sp_mean": statistics.mean(scores), - "sp_median": statistics.median(scores), - "sp_min": min(scores), - "sp_max": max(scores), "recall_mean": statistics.mean(recalls), "precision_mean": statistics.mean(precisions), "f1_mean": statistics.mean(f1s), @@ -200,9 +206,16 @@ def main() -> None: parser.add_argument( "--method", nargs="+", - default=["python_api"], + default=["cli"], choices=["python_api", "cli", "clustalo", "mafft", "muscle"], - help="Alignment method(s) to test (default: python_api)", + help="Alignment method(s) to test (default: cli)", + ) + parser.add_argument( + "--mode", + nargs="+", + default=["default"], + choices=["fast", "default", "recall", "accurate"], + help="Kalign mode preset(s) to test (default: default)", ) parser.add_argument( "--max-cases", @@ -212,15 +225,8 @@ def main() -> None: ) parser.add_argument( "--binary", - default="build/src/kalign", - help="Path to C-compiled kalign binary for CLI method (default: build/src/kalign)", - ) - parser.add_argument( - "--refine", - nargs="+", - default=["none"], - choices=["none", "all", "confident"], - help="Refinement mode(s) to test (default: none)", + default="kalign", + help="Path to kalign binary for CLI method (default: kalign)", ) parser.add_argument( "--threads", @@ -233,22 +239,11 @@ def main() -> None: default="", help="Output JSON file for results", ) - parser.add_argument( - "--adaptive-budget", - action="store_true", - help="Scale trial count by uncertainty", - ) - parser.add_argument( - "--ensemble", - type=int, - default=0, - help="Number of ensemble runs (0 = off)", - ) parser.add_argument( "-j", "--parallel", type=int, default=1, - help="Number of parallel workers for benchmark cases (default: 1)", + help="Number of parallel workers (default: 1)", ) parser.add_argument( "--download-only", @@ -271,13 +266,11 @@ def main() -> None: results = run_benchmark( dataset=args.dataset, methods=args.method, - refine_modes=args.refine, + modes=args.mode, max_cases=args.max_cases, binary=args.binary, n_threads=args.threads, verbose=args.verbose, - adaptive_budget=args.adaptive_budget, - ensemble=args.ensemble, parallel=args.parallel, ) diff --git a/benchmarks/scoring.py b/benchmarks/scoring.py index 63c69a3..dad78b1 100644 --- a/benchmarks/scoring.py +++ b/benchmarks/scoring.py @@ -36,8 +36,8 @@ def to_dict(self) -> dict: def align_with_python_api( - case: BenchmarkCase, output: Path, n_threads: int = 1, refine: str = "none", - adaptive_budget: bool = False, ensemble: int = 0, + case: BenchmarkCase, output: Path, n_threads: int = 1, + mode: str = "default", ) -> float: """Align using kalign Python API. Returns wall time in seconds.""" start = time.perf_counter() @@ -47,9 +47,7 @@ def align_with_python_api( format="fasta", seq_type=case.seq_type, n_threads=n_threads, - refine=refine, - adaptive_budget=adaptive_budget, - ensemble=ensemble, + mode=mode, ) return time.perf_counter() - start @@ -59,20 +57,13 @@ def align_with_cli( output: Path, binary: str = "kalign", n_threads: int = 1, - refine: str = "none", - adaptive_budget: bool = False, - ensemble: int = 0, + mode: str = "default", ) -> float: """Align using kalign C binary via subprocess. Returns wall time in seconds.""" cmd = [binary, "-i", str(case.unaligned), "-f", "fasta", "-o", str(output)] if n_threads > 1: cmd.extend(["--nthreads", str(n_threads)]) - if refine != "none": - cmd.extend(["--refine", refine]) - if adaptive_budget: - cmd.append("--adaptive-budget") - if ensemble > 0: - cmd.extend(["--ensemble", str(ensemble)]) + cmd.extend(["--mode", mode]) start = time.perf_counter() result = subprocess.run(cmd, capture_output=True, text=True) @@ -179,7 +170,7 @@ def score_alignment_detailed(reference: Path, test_output: Path) -> dict: return kalign.compare_detailed(str(reference), str(test_output), column_mask=mask) # For non-BAliBASE references (FASTA format), check for gapless edge case - if reference.suffix in ('.fa', '.fasta') and not _fasta_ref_has_gaps(reference): + if reference.suffix in ('.fa', '.fasta', '.afa') and not _fasta_ref_has_gaps(reference): raise RuntimeError("Reference alignment has no gaps — skipping (trivially aligned)") return kalign.compare_detailed(str(reference), str(test_output), max_gap_frac=-1.0) @@ -190,9 +181,7 @@ def run_case( method: str = "python_api", binary: str = "kalign", n_threads: int = 1, - refine: str = "none", - adaptive_budget: bool = False, - ensemble: int = 0, + mode: str = "default", ) -> AlignmentResult: """Run alignment + scoring for a single benchmark case.""" with tempfile.TemporaryDirectory() as tmpdir: @@ -200,9 +189,11 @@ def run_case( try: if method == "python_api": - wall_time = align_with_python_api(case, output, n_threads, refine, adaptive_budget, ensemble) + wall_time = align_with_python_api( + case, output, n_threads, mode=mode, + ) elif method == "cli": - wall_time = align_with_cli(case, output, binary, n_threads, refine, adaptive_budget, ensemble) + wall_time = align_with_cli(case, output, binary, n_threads, mode=mode) elif method in EXTERNAL_TOOLS: wall_time = align_with_external(case, output, method, n_threads) else: @@ -219,8 +210,8 @@ def run_case( sp_score=sp_score, wall_time=wall_time, seq_type=case.seq_type, - refine="n/a" if is_external else refine, - ensemble=0 if is_external else ensemble, + refine="n/a" if is_external else mode, + ensemble=0, recall=detailed["recall"], precision=detailed["precision"], f1=detailed["f1"], @@ -235,7 +226,7 @@ def run_case( sp_score=0.0, wall_time=0.0, seq_type=case.seq_type, - refine="n/a" if is_external else refine, - ensemble=0 if is_external else ensemble, + refine="n/a" if is_external else mode, + ensemble=0, error=str(e), ) diff --git a/benchmarks/vsm_ensemble_experiment.py b/benchmarks/vsm_ensemble_experiment.py deleted file mode 100644 index 35ecf7c..0000000 --- a/benchmarks/vsm_ensemble_experiment.py +++ /dev/null @@ -1,307 +0,0 @@ -"""Comprehensive BAliBASE benchmark comparing all kalign modes. - -Modes: - 1. baseline - no VSM, no refinement - 2. +vsm - VSM only (vsm_amax=2.0) - 3. +vsm+ref - VSM + refinement - 4. ens3 - ensemble(3), no VSM, no refinement in runs - 5. ens3+vsm - ensemble(3), VSM in each run - 6. ens3+vsm+ref - ensemble(3), VSM + refinement in each run - -Usage: - uv run python -m benchmarks.vsm_ensemble_experiment - uv run python -m benchmarks.vsm_ensemble_experiment --max-cases 10 - uv run python -m benchmarks.vsm_ensemble_experiment --categories RV11 -""" - -import argparse -import json -import statistics -import tempfile -import time -from collections import defaultdict -from pathlib import Path - -import kalign -from .datasets import get_cases -from .scoring import parse_balibase_xml - - -def _read_fasta_seqs(path): - seqs = [] - name = None - buf = [] - with open(path) as f: - for line in f: - line = line.strip() - if not line: - continue - if line.startswith(">"): - if name is not None: - seqs.append((name, "".join(buf))) - name = line[1:].split()[0] - buf = [] - else: - buf.append(line) - if name is not None: - seqs.append((name, "".join(buf))) - return seqs - - -def _alignment_stats(path): - seqs = _read_fasta_seqs(path) - if not seqs: - return {} - alnlen = len(seqs[0][1]) - nseq = len(seqs) - total_chars = 0 - total_gaps = 0 - total_gap_opens = 0 - for _, s in seqs: - in_gap = False - for ch in s: - if ch == "-": - total_gaps += 1 - if not in_gap: - total_gap_opens += 1 - in_gap = True - else: - total_chars += 1 - in_gap = False - gap_frac = total_gaps / (nseq * alnlen) if alnlen > 0 else 0 - mean_seqlen = total_chars / nseq if nseq > 0 else 0 - alnlen_ratio = alnlen / mean_seqlen if mean_seqlen > 0 else 0 - return { - "alnlen": alnlen, - "nseq": nseq, - "gap_frac": gap_frac, - "gap_opens_per_seq": total_gap_opens / nseq if nseq > 0 else 0, - "alnlen_ratio": alnlen_ratio, - "mean_seqlen": mean_seqlen, - } - - -def _score_case(case, output_path): - xml_path = case.reference.with_suffix(".xml") - if xml_path.exists(): - mask = parse_balibase_xml(xml_path) - return kalign.compare_detailed(str(case.reference), str(output_path), column_mask=mask) - return kalign.compare_detailed(str(case.reference), str(output_path)) - - -CONFIGS = [ - { - "label": "baseline", - "refine": "none", - "vsm_amax": 0.0, # explicitly disable VSM - }, - { - "label": "+vsm", - "refine": "none", - "vsm_amax": 2.0, - }, - { - "label": "+vsm+ref", - "refine": "confident", - "vsm_amax": 2.0, - }, - { - "label": "+vsm+iref", - "refine": "inline", - "vsm_amax": 2.0, - }, - { - "label": "ens3", - "ensemble": 3, - "refine": "none", - "vsm_amax": 0.0, # no VSM in individual runs - }, - { - "label": "ens3+vsm", - "ensemble": 3, - "refine": "none", - "vsm_amax": 2.0, - }, - { - "label": "ens3+vsm+ref", - "ensemble": 3, - "refine": "confident", - "vsm_amax": 2.0, - }, - { - "label": "ens3+vsm+ref+ra1", - "ensemble": 3, - "refine": "confident", - "vsm_amax": 2.0, - "realign": 1, - }, - { - "label": "ens3+vsm+ref+ra2", - "ensemble": 3, - "refine": "confident", - "vsm_amax": 2.0, - "realign": 2, - }, - { - "label": "ens3+vsm+iref", - "ensemble": 3, - "refine": "inline", - "vsm_amax": 2.0, - }, - { - "label": "ens3+vsm+iref+ra1", - "ensemble": 3, - "refine": "inline", - "vsm_amax": 2.0, - "realign": 1, - }, -] - - -def _run_one(case, config): - label = config["label"] - with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as tmp: - tmp_path = tmp.name - try: - t0 = time.perf_counter() - kwargs = dict(format="fasta", seq_type=case.seq_type) - - # Alignment mode - if config.get("ensemble"): - kwargs["ensemble"] = config["ensemble"] - kwargs["refine"] = config.get("refine", "none") - kwargs["vsm_amax"] = config.get("vsm_amax", -1.0) - if config.get("realign"): - kwargs["realign"] = config["realign"] - else: - # Standard kalign - kwargs["refine"] = config.get("refine", "none") - kwargs["vsm_amax"] = config.get("vsm_amax", -1.0) - - kalign.align_file_to_file(str(case.unaligned), tmp_path, **kwargs) - wall = time.perf_counter() - t0 - scores = _score_case(case, tmp_path) - stats = _alignment_stats(tmp_path) - return { - "family": case.family, "dataset": case.dataset, - "method": label, - "recall": scores["recall"], "precision": scores["precision"], - "f1": scores["f1"], "tc": scores["tc"], - "wall_time": wall, - **stats, - } - except Exception as e: - return { - "family": case.family, "dataset": case.dataset, - "method": label, - "recall": 0, "precision": 0, "f1": 0, "tc": 0, - "wall_time": 0, - "error": str(e), - } - finally: - Path(tmp_path).unlink(missing_ok=True) - - -def _print_summary(results, method_names): - print(f"\n{'Method':>20} {'Recall':>8} {'Prec':>8} {'F1':>8} {'TC':>8}" - f" {'GapFrac':>8} {'AlnRatio':>9} {'Time':>7}") - print("-" * 90) - groups = defaultdict(list) - for r in results: - groups[r["method"]].append(r) - for method in method_names: - entries = groups.get(method, []) - valid = [r for r in entries if "error" not in r] - errs = len(entries) - len(valid) - if not valid: - print(f"{method:>20} (no results)") - continue - rec = statistics.mean(r["recall"] for r in valid) - prec = statistics.mean(r["precision"] for r in valid) - f1 = statistics.mean(r["f1"] for r in valid) - tc = statistics.mean(r["tc"] for r in valid) - gf = statistics.mean(r.get("gap_frac", 0) for r in valid) - ar = statistics.mean(r.get("alnlen_ratio", 0) for r in valid) - wt = sum(r.get("wall_time", 0) for r in valid) - suffix = f" ({errs} err)" if errs else "" - print(f"{method:>20} {rec:>8.3f} {prec:>8.3f} {f1:>8.3f} {tc:>8.3f}" - f" {gf:>8.3f} {ar:>9.2f} {wt:>6.1f}s{suffix}") - - -def _print_per_category(results, method_names): - all_cats = sorted({r["dataset"].replace("balibase_", "") for r in results}) - for cat in all_cats: - cat_results = [r for r in results if cat in r["dataset"]] - cat_groups = defaultdict(list) - for r in cat_results: - cat_groups[r["method"]].append(r) - - n = len(cat_groups.get(method_names[0], [])) - print(f"\n=== {cat} ({n} cases) ===") - print(f"{'Method':>20} {'Recall':>8} {'Prec':>8} {'F1':>8} {'TC':>8}") - print("-" * 55) - for method in method_names: - entries = [r for r in cat_groups.get(method, []) if "error" not in r] - if not entries: - continue - rec = statistics.mean(r["recall"] for r in entries) - prec = statistics.mean(r["precision"] for r in entries) - f1 = statistics.mean(r["f1"] for r in entries) - tc = statistics.mean(r["tc"] for r in entries) - print(f"{method:>20} {rec:>8.3f} {prec:>8.3f} {f1:>8.3f} {tc:>8.3f}") - - -def main(): - parser = argparse.ArgumentParser(description="Comprehensive BAliBASE benchmark") - parser.add_argument("--max-cases", type=int, default=0) - parser.add_argument("--categories", nargs="*", default=None) - parser.add_argument("--configs", nargs="*", default=None, - help="Only run specific configs by label") - args = parser.parse_args() - - cases = get_cases("balibase", max_cases=args.max_cases if args.max_cases else None) - if args.categories: - cats = [c.upper() for c in args.categories] - cases = [c for c in cases if any(cat in c.dataset.upper() for cat in cats)] - - configs = CONFIGS - if args.configs: - configs = [c for c in CONFIGS if c["label"] in args.configs] - - print(f"{len(cases)} BAliBASE cases, {len(configs)} configs", flush=True) - - method_names = [c["label"] for c in configs] - n_tasks = len(configs) * len(cases) - print(f"{n_tasks} total tasks (sequential)", flush=True) - - t0 = time.perf_counter() - results = [] - done = 0 - - for cfg in configs: - print(f"\n Config: {cfg['label']}", flush=True) - for case in cases: - r = _run_one(case, cfg) - results.append(r) - done += 1 - if done % 20 == 0: - elapsed = time.perf_counter() - t0 - eta = elapsed / done * (n_tasks - done) - print(f" {done}/{n_tasks} ({elapsed:.0f}s, ETA {eta:.0f}s)", flush=True) - - elapsed = time.perf_counter() - t0 - print(f"\nAll done in {elapsed:.0f}s") - - _print_summary(results, method_names) - _print_per_category(results, method_names) - - # Save - out = Path("benchmarks/data/vsm_ensemble_experiment.json") - out.parent.mkdir(parents=True, exist_ok=True) - with open(out, "w") as f: - json.dump(results, f, indent=2) - print(f"\nSaved to {out}") - - -if __name__ == "__main__": - main() diff --git a/build.zig b/build.zig index 63f80b8..1092133 100644 --- a/build.zig +++ b/build.zig @@ -1,52 +1,60 @@ const std = @import("std"); -const builtin = @import("builtin"); -const ArrayList = std.ArrayList; -const kalignPackageVersion = "3.5.1"; +const kalignPackageVersion = "3.5.2"; const targets: []const std.Target.Query = &.{ .{ .cpu_arch = .aarch64, .os_tag = .macos }, .{ .cpu_arch = .aarch64, .os_tag = .linux }, .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu }, .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }, - // .{ .cpu_arch = .x86_64, .os_tag = .windows }, }; const cflags = [_][]const u8{ - "-DKALIGN_PACKAGE_VERSION=\"3.5.0\"", + "-DKALIGN_PACKAGE_VERSION=\"" ++ kalignPackageVersion ++ "\"", "-DKALIGN_PACKAGE_NAME=\"kalign\"", "-DKALIGN_ALN_SERIAL_THRESHOLD=250", "-DKALIGN_KMEANS_UPGMA_THRESHOLD=50", }; pub fn build(b: *std.Build) !void { - // const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); for (targets) |t| { - const lib = b.addStaticLibrary(.{ - .name = "tldevel", + // --- Static library module --- + const lib_mod = b.createModule(.{ .target = b.resolveTargetQuery(t), .optimize = optimize, + .link_libc = true, + }); + lib_mod.addIncludePath(b.path("lib/src")); + lib_mod.addIncludePath(b.path("lib/include")); + lib_mod.addCSourceFiles(.{ .files = &kalign_lib_sources, .flags = &cflags }); + + const lib = b.addLibrary(.{ + .name = "tldevel", + .linkage = .static, + .root_module = lib_mod, }); - lib.linkLibC(); - lib.addIncludePath(.{ .path = "./lib/src" }); - lib.addCSourceFiles(.{ .files = &kalign_lib_sources, .flags = &cflags }); - lib.addIncludePath(.{ .path = "./lib/include" }); b.installArtifact(lib); - const kalign_bin = b.addExecutable(.{ - .name = "kalign", + // --- Executable module --- + const bin_mod = b.createModule(.{ .target = b.resolveTargetQuery(t), .optimize = optimize, + .link_libc = true, }); + bin_mod.addIncludePath(b.path("lib/src")); + bin_mod.addIncludePath(b.path("lib/include")); + bin_mod.addCSourceFiles(.{ .files = &kalign_sources, .flags = &cflags }); + bin_mod.linkLibrary(lib); - lib.addCSourceFiles(.{ .files = &kalign_sources, .flags = &cflags }); - kalign_bin.addIncludePath(.{ .path = "./lib/src" }); - kalign_bin.addIncludePath(.{ .path = "./lib/include" }); - kalign_bin.linkLibrary(lib); + const kalign_bin = b.addExecutable(.{ + .name = "kalign", + .root_module = bin_mod, + }); b.installArtifact(kalign_bin); + // Install into a per-target subdirectory (e.g. zig-out/x86_64-linux-gnu/kalign) const target_output = b.addInstallArtifact(kalign_bin, .{ .dest_dir = .{ .override = .{ @@ -57,25 +65,9 @@ pub fn build(b: *std.Build) !void { b.getInstallStep().dependOn(&target_output.step); } - - // const exe = b.addExecutable(.{ - // .name = "zig_test", - // .target = target, - // .optimize = optimize, - // }); - // exe.addCSourceFile(.{ .file = .{ .path = "./tests/zig_test.c" }, .flags = &[_][]const u8{"-std=c99"} }); - - // // exe.addCSourceFiles(.{ .files = &kalign_lib_sources, .flags = &[_][]const u8{"-std=c99"} }); - // // exe.addCSourceFile(&.{"./tests/zig_test.c"}, cflags.items); - // // exe.addCSourceFile("./lib/src/strnlen_compat.c", cflags.items); - // exe.addIncludePath(.{ .path = "./lib/src" }); - // // exe.linkLibrary(lib); - // exe.linkLibC(); - // b.installArtifact(exe); } const kalign_lib_sources = [_][]const u8{ - // "lib/src/strnlen_compat.c", "lib/src/test.c", "lib/src/tldevel.c", "lib/src/tlmisc.c", @@ -97,6 +89,7 @@ const kalign_lib_sources = [_][]const u8{ "lib/src/pick_anchor.c", "lib/src/aln_wrap.c", "lib/src/aln_apair_dist.c", + "lib/src/aln_add.c", "lib/src/aln_param.c", "lib/src/aln_run.c", "lib/src/aln_mem.c", @@ -111,10 +104,11 @@ const kalign_lib_sources = [_][]const u8{ "lib/src/poar.c", "lib/src/consensus_msa.c", "lib/src/anchor_consistency.c", + "lib/src/msa_consistency.c", "lib/src/ensemble.c", }; const kalign_sources = [_][]const u8{ - "./src/run_kalign.c", - "./src/parameters.c", + "src/run_kalign.c", + "src/parameters.c", }; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4292510..51825e8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -39,6 +39,7 @@ set(source_files src/aln_wrap.c src/aln_apair_dist.c + src/aln_add.c src/aln_param.c src/aln_run.c @@ -54,23 +55,30 @@ set(source_files src/poar.c src/consensus_msa.c src/anchor_consistency.c + src/msa_consistency.c src/ensemble.c - - # src/coretralign.c - # src/test.h ) +# Add threadpool source when enabled +if(USE_THREADPOOL) + list(APPEND source_files src/threadpool/threadpool.c) +endif() + add_library(${PROJECT_NAME}_OBJ OBJECT ${source_files}) -if(OpenMP_C_FOUND) +if(OpenMP_C_FOUND AND NOT USE_THREADPOOL) target_link_libraries(${PROJECT_NAME}_OBJ PRIVATE OpenMP::OpenMP_C) -endif(OpenMP_C_FOUND) +endif() +if(USE_THREADPOOL) + target_link_libraries(${PROJECT_NAME}_OBJ PRIVATE Threads::Threads) +endif() target_include_directories(${PROJECT_NAME}_OBJ PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/threadpool ) target_compile_definitions(${PROJECT_NAME}_OBJ PRIVATE KALIGN_PACKAGE_VERSION=\"${KALIGN_PACKAGE_VERSION}\") @@ -126,11 +134,12 @@ target_include_directories(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME} PRIVATE m) -if(OpenMP_C_FOUND) +if(OpenMP_C_FOUND AND NOT USE_THREADPOOL) target_link_libraries(${PROJECT_NAME} PRIVATE OpenMP::OpenMP_C) endif() - - +if(USE_THREADPOOL) + target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) +endif() add_library(${PROJECT_NAME}_static STATIC ${public_header_files} @@ -163,9 +172,12 @@ target_include_directories(${PROJECT_NAME}_static target_link_libraries(${PROJECT_NAME}_static PRIVATE m) -if(OpenMP_C_FOUND) +if(OpenMP_C_FOUND AND NOT USE_THREADPOOL) target_link_libraries(${PROJECT_NAME}_static PRIVATE OpenMP::OpenMP_C) endif() +if(USE_THREADPOOL) + target_link_libraries(${PROJECT_NAME}_static PRIVATE Threads::Threads) +endif() add_library(tldevel STATIC diff --git a/lib/include/kalign/kalign.h b/lib/include/kalign/kalign.h index 553bda5..ecb97fe 100644 --- a/lib/include/kalign/kalign.h +++ b/lib/include/kalign/kalign.h @@ -2,6 +2,7 @@ #define KALIGN_H #include +#include #ifdef KALIGN_IMPORT #define EXTERN @@ -15,15 +16,32 @@ #endif #endif -#define KALIGN_TYPE_DNA 0 -#define KALIGN_TYPE_DNA_INTERNAL 1 -#define KALIGN_TYPE_RNA 2 -#define KALIGN_TYPE_PROTEIN 3 -#define KALIGN_TYPE_PROTEIN_DIVERGENT 4 -#define KALIGN_TYPE_PROTEIN_PFASUM43 5 -#define KALIGN_TYPE_PROTEIN_PFASUM60 6 -#define KALIGN_TYPE_PROTEIN_PFASUM_AUTO 7 -#define KALIGN_TYPE_UNDEFINED 8 +/* Substitution matrix constants. + Every value maps to exactly one scoring table. No duplicates. + Value 4 is reserved for legacy GONNET (KALIGN_TYPE_PROTEIN_DIVERGENT). */ +#define KALIGN_MATRIX_AUTO 0 /* auto-select for biotype */ +#define KALIGN_MATRIX_PFASUM43 1 /* 1/3 bit, divergent protein */ +#define KALIGN_MATRIX_PFASUM60 2 /* 1/3 bit, moderate protein */ +#define KALIGN_MATRIX_CORBLOSUM66 3 /* 1/3 bit, close protein */ +#define KALIGN_MATRIX_DNA 5 /* DNA match/mismatch (+5/-4) */ +#define KALIGN_MATRIX_DNA_INTERNAL 6 /* DNA internal (tgpe=8) */ +#define KALIGN_MATRIX_RNA 7 /* RNA RIBOSUM-like (~160-383) */ +#define KALIGN_MATRIX_NUC_1PAM 10 /* Kimura 1PAM, kappa=2 (close) */ +#define KALIGN_MATRIX_NUC_20PAM 11 /* Kimura 20PAM, kappa=2 (moderate)*/ +#define KALIGN_MATRIX_NUC_200PAM 12 /* Kimura 200PAM, kappa=2 (distant)*/ + +/* Backward compatibility — old KALIGN_TYPE_* map to KALIGN_MATRIX_*. + KALIGN_TYPE_PROTEIN_DIVERGENT stays at 4 (GONNET, dead code). */ +#define KALIGN_TYPE_DNA KALIGN_MATRIX_DNA +#define KALIGN_TYPE_DNA_INTERNAL KALIGN_MATRIX_DNA_INTERNAL +#define KALIGN_TYPE_RNA KALIGN_MATRIX_RNA +#define KALIGN_TYPE_PROTEIN KALIGN_MATRIX_PFASUM43 +#define KALIGN_TYPE_PROTEIN_DIVERGENT 4 /* GONNET — dead code, do not use */ +#define KALIGN_TYPE_PROTEIN_PFASUM43 KALIGN_MATRIX_PFASUM43 +#define KALIGN_TYPE_PROTEIN_PFASUM60 KALIGN_MATRIX_PFASUM60 +#define KALIGN_TYPE_PROTEIN_PFASUM_AUTO KALIGN_MATRIX_AUTO +#define KALIGN_TYPE_UNDEFINED KALIGN_MATRIX_AUTO +#define KALIGN_TYPE_PROTEIN_CORBLOSUM66 KALIGN_MATRIX_CORBLOSUM66 #define KALIGN_REFINE_NONE 0 #define KALIGN_REFINE_ALL 1 @@ -34,6 +52,7 @@ struct msa; /* input output routines */ EXTERN int kalign_read_input(char* infile, struct msa** msa,int quiet); +EXTERN int kalign_read_sequences(char* infile, struct msa** msa, int quiet); EXTERN int kalign_write_msa(struct msa *msa, char *outfile, char *format); @@ -42,56 +61,33 @@ EXTERN int kalign_write_msa(struct msa *msa, char *outfile, char *format); /* EXTERN int kalign_arr_to_msa(char **input_sequences, int *len, int numseq, struct msa **multiple_aln); */ +/* Legacy C API — thin wrapper around kalign_align_full */ EXTERN int kalign(char **seq, int *len, int numseq, int n_threads, int type, float gpo, float gpe, float tgpe, char ***aligned, int *out_aln_len); -EXTERN int kalign_run(struct msa *msa, int n_threads, int type, float gpo, float gpe, float tgpe, int refine, int adaptive_budget); - -EXTERN int kalign_run_seeded(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - uint64_t tree_seed, float tree_noise, - float dist_scale, float vsm_amax, - float use_seq_weights, - int consistency_anchors, float consistency_weight); - -EXTERN int kalign_run_dist_scale(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - float use_seq_weights); - -EXTERN int kalign_run_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights, - int consistency_anchors, float consistency_weight); - -EXTERN int kalign_post_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights); - -EXTERN int kalign_ensemble(struct msa* msa, int n_threads, int type, - int n_runs, float gpo, float gpe, float tgpe, - uint64_t seed, int min_support, - const char* save_poar_path, - int refine, float dist_scale, float vsm_amax, - int realign, float use_seq_weights, - int consistency_anchors, float consistency_weight); - EXTERN int kalign_consensus_from_poar(struct msa* msa, const char* poar_path, int min_support); +/* Add sequences to existing alignment */ +EXTERN int kalign_add_sequences(struct msa* existing, + struct msa* new_seqs, + int n_threads); + +/* Confidence masking */ +#define KALIGN_MASK_LOWERCASE 0 +#define KALIGN_MASK_REMOVE 1 + +EXTERN int kalign_mask_by_confidence(struct msa* msa, float threshold, int style); +EXTERN int kalign_write_confidence(struct msa* msa, const char* path); + /* Memory */ EXTERN void kalign_free_msa(struct msa* msa); +/* Query MSA properties */ +EXTERN int kalign_msa_get_biotype(struct msa *msa); + /* Auxillary... */ EXTERN int reformat_settings_msa(struct msa *msa, int rename, int unalign); @@ -107,6 +103,54 @@ EXTERN int kalign_msa_compare_detailed(struct msa *r, struct msa *t, EXTERN int kalign_msa_compare_with_mask(struct msa *r, struct msa *t, int *scored_cols, int n_cols, struct poar_score *out); + +/* Unified alignment entry point — all callers should use this. + * + * NOTE: kalign_run_config_defaults() returns a *protein-oriented* config + * (matrix = KALIGN_MATRIX_PFASUM43, protein gap penalties). Callers that + * may operate on DNA or RNA input should set cfg.matrix = KALIGN_MATRIX_AUTO + * before passing the config to kalign_align_full; the library will then + * resolve to a biotype-appropriate matrix at alignment time. The mode-preset + * path (kalign_get_mode_preset) populates the matrix per biotype automatically. */ +EXTERN struct kalign_run_config kalign_run_config_defaults(void); +EXTERN struct kalign_ensemble_config kalign_ensemble_config_defaults(void); + +EXTERN int kalign_align_full(struct msa* msa, + const struct kalign_run_config* runs, + int n_runs, + const struct kalign_ensemble_config* ens, + int n_threads); + +/* Expand a base config into n_runs configs using the built-in diversity table. + Caller must pass resolved (non-sentinel) gap penalties in base. + Caller allocates out[n_runs]. */ +EXTERN int kalign_generate_ensemble_runs(const struct kalign_run_config* base, + int n_runs, uint64_t seed, + struct kalign_run_config* out); + +/* Get a built-in mode preset. + * + * Presets were derived from NSGA-III multi-objective optimization + * (objectives: F1, TC, wall_time) with 5-fold cross-validation on + * BAliBASE v4 (protein) and BRAliBASE (RNA). + * + * mode: "fast", "default", "recall", or "accurate" (case-insensitive). + * NULL is treated as "default". + * biotype: ALN_BIOTYPE_PROTEIN, ALN_BIOTYPE_DNA, or ALN_BIOTYPE_RNA. + * Determines which preset grid slot to use. + * runs: caller-allocated array of at least KALIGN_MAX_PRESET_RUNS configs. + * n_runs: filled with the number of runs in the preset. + * ens: filled with ensemble config (only meaningful when *n_runs > 1). + * + * Returns 0 on success, -1 if mode is unknown. */ +#define KALIGN_MAX_PRESET_RUNS 8 + +EXTERN int kalign_get_mode_preset(const char *mode, + int biotype, + struct kalign_run_config *runs, + int *n_runs, + struct kalign_ensemble_config *ens); + #undef KALIGN_IMPORT #undef EXTERN diff --git a/lib/include/kalign/kalign_config.h b/lib/include/kalign/kalign_config.h new file mode 100644 index 0000000..21fc9e2 --- /dev/null +++ b/lib/include/kalign/kalign_config.h @@ -0,0 +1,36 @@ +#ifndef KALIGN_CONFIG_H +#define KALIGN_CONFIG_H + +#include +#include + +/* Per-run alignment configuration. + Each field controls one aspect of a single alignment run. + All values are concrete — no sentinel values. + Use kalign_run_config_defaults() for sensible PFASUM43 protein defaults. */ +struct kalign_run_config { + int matrix; /* KALIGN_MATRIX_* constant (AUTO = auto-detect) */ + float gpo; /* gap open penalty */ + float gpe; /* gap extend penalty */ + float tgpe; /* terminal gap extend penalty */ + float vsm_amax; /* variable scoring matrix amplitude (0 = off) */ + float seq_weights; /* profile rebalancing pseudo-count (0 = off) */ + float dist_scale; /* distance-dependent gap scaling (0 = off) */ + int refine; /* KALIGN_REFINE_* constant (default: NONE) */ + int adaptive_budget; /* scale refinement trials by uncertainty (0=off)*/ + int realign; /* iterative tree-rebuild iterations (0 = off) */ + uint64_t tree_seed; /* random seed for guide tree perturbation */ + float tree_noise; /* guide tree perturbation sigma (0.0 = none) */ + int consistency_anchors; /* anchor consistency rounds (0 = off) */ + float consistency_weight; /* anchor consistency bonus weight (default: 2.0)*/ +}; + +/* Ensemble orchestration configuration. + Only used when n_runs > 1. */ +struct kalign_ensemble_config { + int min_support; /* POAR consensus threshold (0 = auto) */ + int consistency_merge; /* 0 = POAR (default), 1 = MSA consistency merge */ + float consistency_merge_weight; /* MSA consistency bonus weight (default: 2.0)*/ +}; + +#endif diff --git a/lib/src/aln_add.c b/lib/src/aln_add.c new file mode 100644 index 0000000..e9b96b7 --- /dev/null +++ b/lib/src/aln_add.c @@ -0,0 +1,379 @@ +/* aln_add.c — Add new sequences to an existing alignment. + * + * Builds a consensus profile from the existing aligned sequences, then + * aligns each new sequence against it via seq-to-profile Hirschberg DP. + * The existing sequences are NOT modified — only gap characters are + * inserted into the new sequences to fit the existing column structure. + */ + +#include "tldevel.h" +#include +#include +#include + +#include "msa_struct.h" +#include "msa_alloc.h" +#include "msa_op.h" +#include "msa_check.h" +#include "alphabet.h" + +#include "aln_param.h" +#include "aln_struct.h" +#include "aln_mem.h" +#include "aln_setup.h" +#include "aln_controller.h" +#include "kalign/kalign.h" + +#ifdef USE_THREADPOOL +#include "threadpool/threadpool.h" +#endif + +#define ALN_ADD_IMPORT +#include "aln_add.h" + +/* Map a character (from finalized alignment) to internal index 0-22. + Returns -1 for gaps and unknown characters. + Uses the standard kalign protein alphabet: ARNDCQEGHILKMFPSTWYVBZX */ +static int char_to_internal(char c, int biotype) +{ + static const int protein_map[128] = { + /* 0-15 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + /* 16-31 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + /* 32-47 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + /* 48-63 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + /* @ABCDE */ -1, 0,20, 4, 3, 6,13, 7, 8, 9,-1,11,10,12, 2,-1, + /* PQRSTU */ 14, 5, 1,15,16,22,19,17,22,18,21,-1,-1,-1,-1,-1, + /* `abcde */ -1, 0,20, 4, 3, 6,13, 7, 8, 9,-1,11,10,12, 2,-1, + /* pqrstu */ 14, 5, 1,15,16,22,19,17,22,18,21,-1,-1,-1,-1,-1, + }; + static const int dna_map[128] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1, 0,-1, 1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1, 0,-1, 1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + if(c < 0 || c == '-') return -1; + if(biotype == ALN_BIOTYPE_DNA){ + return dna_map[(unsigned char)c]; + } + return protein_map[(unsigned char)c]; +} + +/* Build a consensus profile from an existing finalized alignment. + Profile has alnlen columns in the standard 64-float-per-column format. + Residue frequencies are summed; substitution scores are weighted averages. */ +static int build_consensus_profile(struct msa* existing, + struct aln_param* ap, + float** prof_out) +{ + float* prof = NULL; + float** subm = ap->subm; + float gpo = ap->gpo; + float gpe = ap->gpe; + float tgpe = ap->tgpe; + int alnlen = existing->alnlen; + int numseq = existing->numseq; + int biotype = existing->biotype; + int i, j, col; + + MMALLOC(prof, sizeof(float) * (alnlen + 2) * 64); + + /* Point to trailing boundary row */ + prof += (64 * (alnlen + 1)); + + /* Trailing boundary */ + for(i = 0; i < 64; i++) prof[i] = 0; + prof[23 + 32] = -gpo; + prof[24 + 32] = -gpe; + prof[25 + 32] = -tgpe; + prof[55] = -gpo; + prof[56] = -gpe; + prof[57] = -tgpe; + + /* Fill each column from alnlen-1 down to 0 */ + for(col = alnlen - 1; col >= 0; col--){ + prof -= 64; + for(j = 0; j < 64; j++) prof[j] = 0; + + /* Count residue frequencies at this column */ + float freq[23]; + for(j = 0; j < 23; j++) freq[j] = 0.0f; + float total = 0.0f; + + for(i = 0; i < numseq; i++){ + char c = existing->sequences[i]->seq[col]; + int idx = char_to_internal(c, biotype); + if(idx >= 0 && idx < 23){ + freq[idx] += 1.0f; + total += 1.0f; + } + } + + /* Store frequency counts in positions 0-22 */ + for(j = 0; j < 23; j++){ + prof[j] = freq[j]; + } + + /* Compute substitution scores as weighted average over + observed residues: score[j] = sum(freq[c] * subm[c][j]) / total */ + prof += 32; + if(total > 0.0f){ + for(j = 0; j < 23; j++){ + float score = 0.0f; + for(int c = 0; c < 23; c++){ + if(freq[c] > 0.0f){ + score += freq[c] * subm[c][j]; + } + } + prof[j] = score / total; + } + } + /* Gap penalties */ + prof[23] = -gpo; + prof[24] = -gpe; + prof[25] = -tgpe; + prof -= 32; + + /* Store base penalties for set_gap_penalties_n */ + prof[55] = -gpo; + prof[56] = -gpe; + prof[57] = -tgpe; + } + + /* Leading boundary */ + prof -= 64; + for(i = 0; i < 64; i++) prof[i] = 0; + prof[23 + 32] = -gpo; + prof[24 + 32] = -gpe; + prof[25 + 32] = -tgpe; + prof[55] = -gpo; + prof[56] = -gpe; + prof[57] = -tgpe; + + *prof_out = prof; + return OK; +ERROR: + return FAIL; +} + +/* Align one new sequence against the consensus profile and produce + a gapped sequence string matching the existing alignment columns. + Returns a newly allocated string (caller must free). */ +static int align_one_to_profile(struct aln_param* ap, + float* cons_profile, + int profile_len, + int n_existing, + const uint8_t* new_seq, + int new_len, + char* new_seq_chars, + char** gapped_out) +{ + struct aln_mem* m = NULL; + char* gapped = NULL; + int i, c; + int pos_seq; + + RUN(alloc_aln_mem(&m, 256)); + m->ap = ap; + m->mode = ALN_MODE_FULL; + m->run_parallel = 0; + m->flip_threshold = 0.0F; + m->flip_trial = 0; + m->flip_stride = 1; + m->flip_counter = 0; + m->flip_mask = 0; + m->margin_sum = 0.0F; + m->margin_count = 0; + + /* Profile is always "seq1" (the longer axis in Hirschberg). + New sequence is "seq2". */ + m->len_a = profile_len; + m->len_b = new_len; + m->enda = profile_len; + m->endb = new_len; + + m->seq1 = NULL; /* not a sequence — it's a profile */ + m->seq2 = new_seq; + m->prof1 = cons_profile; + m->prof2 = NULL; + m->sip = n_existing; + m->consistency = NULL; + + m->f[0].a = 0.0F; + m->f[0].ga = -FLT_MAX; + m->f[0].gb = -FLT_MAX; + m->b[0].a = 0.0F; + m->b[0].ga = -FLT_MAX; + m->b[0].gb = -FLT_MAX; + + /* Scale gap penalties in profile by n_existing */ + RUN(set_gap_penalties_n(cons_profile, profile_len, n_existing)); + + RUN(init_alnmem(m)); + aln_runner(m); + RUN(add_gap_info_to_path_n(m)); + + /* Build gapped sequence from alignment path. + path[0] = alignment length + path[c]: 0=match, &1=gap in profile (insertion in new seq — SKIP in strict mode), + &2=gap in new seq (insert '-'), 3=end */ + MMALLOC(gapped, sizeof(char) * (m->path[0] + 2)); + memset(gapped, '-', m->path[0] + 1); + gapped[m->path[0] + 1] = '\0'; + + pos_seq = 0; + i = 0; + c = 1; + while(m->path[c] != 3){ + if(m->path[c] == 0){ + /* Match: new seq residue aligns to profile column */ + if(pos_seq < new_len){ + gapped[i] = new_seq_chars[pos_seq]; + }else{ + gapped[i] = '-'; + } + pos_seq++; + i++; + }else if(m->path[c] & 1){ + /* Gap in profile = insertion in new seq. + Strict mode: skip this residue (don't add new columns). */ + pos_seq++; + /* Don't increment i — residue is dropped */ + }else if(m->path[c] & 2){ + /* Gap in new seq: insert gap at this profile column */ + gapped[i] = '-'; + i++; + } + c++; + } + gapped[i] = '\0'; + + /* Verify: gapped length should equal profile_len (strict mode) */ + if(i != profile_len){ + /* May differ slightly — pad or truncate */ + while(i < profile_len){ + gapped[i] = '-'; + i++; + } + gapped[profile_len] = '\0'; + } + + free_aln_mem(m); + *gapped_out = gapped; + return OK; +ERROR: + if(m) free_aln_mem(m); + if(gapped) MFREE(gapped); + return FAIL; +} + +int kalign_add_sequences(struct msa* existing, + struct msa* new_seqs, + int n_threads) +{ + struct aln_param* ap = NULL; + float* cons_profile = NULL; + int i; + int numseq_existing; + int numseq_new; + int alnlen; + + ASSERT(existing != NULL, "No existing alignment"); + ASSERT(new_seqs != NULL, "No new sequences"); + /* Finalize if not already — ensures seq->seq has gap characters and alnlen is set */ + if(existing->aligned != ALN_STATUS_FINAL){ + if(existing->aligned == ALN_STATUS_ALIGNED){ + RUN(finalise_alignment(existing)); + }else{ + ERROR_MSG("Existing MSA must be aligned (status=%d)", existing->aligned); + } + } + + numseq_existing = existing->numseq; + numseq_new = new_seqs->numseq; + alnlen = existing->alnlen; + + if(numseq_new == 0){ + return OK; /* nothing to add */ + } + + /* Detect biotype if needed */ + if(existing->biotype == ALN_BIOTYPE_UNDEF){ + RUN(detect_alphabet(existing)); + } + if(new_seqs->biotype == ALN_BIOTYPE_UNDEF){ + new_seqs->biotype = existing->biotype; + } + + /* Encode new sequences to internal representation */ + if(existing->biotype == ALN_BIOTYPE_DNA){ + new_seqs->L = ALPHA_defDNA; + RUN(convert_msa_to_internal(new_seqs, ALPHA_defDNA)); + }else{ + new_seqs->L = ALPHA_ambigiousPROTEIN; + RUN(convert_msa_to_internal(new_seqs, ALPHA_ambigiousPROTEIN)); + } + + /* Init alignment parameters */ + RUN(aln_param_init(&ap, existing->biotype, n_threads, + KALIGN_MATRIX_AUTO, -1.0f, -1.0f, -1.0f)); + + /* Build consensus profile from existing alignment */ + RUN(build_consensus_profile(existing, ap, &cons_profile)); + + /* Align each new sequence to the consensus profile */ + for(i = 0; i < numseq_new; i++){ + char* gapped = NULL; + + RUN(align_one_to_profile( + ap, cons_profile, alnlen, numseq_existing, + new_seqs->sequences[i]->s, + new_seqs->sequences[i]->len, + new_seqs->sequences[i]->seq, + &gapped)); + + /* Replace the new sequence's seq with the gapped version */ + MFREE(new_seqs->sequences[i]->seq); + new_seqs->sequences[i]->seq = gapped; + } + + /* Append new sequences to existing MSA (manual, not merge_msa which + may change alignment properties). */ + { + int total = numseq_existing + numseq_new; + if(total > existing->alloc_numseq){ + MREALLOC(existing->sequences, + sizeof(struct msa_seq*) * total); + existing->alloc_numseq = total; + } + for(i = 0; i < numseq_new; i++){ + /* Transfer ownership of the new sequence from new_seqs to existing */ + existing->sequences[numseq_existing + i] = new_seqs->sequences[i]; + new_seqs->sequences[i] = NULL; + /* Update len to alnlen (gapped length) */ + existing->sequences[numseq_existing + i]->len = + (int)strlen(existing->sequences[numseq_existing + i]->seq); + } + existing->numseq = total; + } + existing->alnlen = alnlen; + existing->aligned = ALN_STATUS_FINAL; + + /* Cleanup */ + /* Free the profile — need to adjust pointer back to allocation start */ + { + float* prof_base = cons_profile; /* already points to start (leading boundary) */ + MFREE(prof_base); + } + aln_param_free(ap); + + return OK; +ERROR: + if(cons_profile) MFREE(cons_profile); + if(ap) aln_param_free(ap); + return FAIL; +} diff --git a/lib/src/aln_add.h b/lib/src/aln_add.h new file mode 100644 index 0000000..cbb8b5b --- /dev/null +++ b/lib/src/aln_add.h @@ -0,0 +1,38 @@ +#ifndef ALN_ADD_H +#define ALN_ADD_H + +#ifdef ALN_ADD_IMPORT +#define EXTERN +#else +#ifdef __cplusplus +#define EXTERN extern "C" +#else +#define EXTERN extern +#endif +#endif + +struct msa; + +/* Add new unaligned sequences to an existing finalized alignment. + * + * Each new sequence is independently aligned against a consensus profile + * built from the existing alignment. The existing sequences are NOT + * modified — their gaps are preserved exactly. + * + * After this call, existing->sequences contains the original sequences + * (unchanged) plus the new sequences (with gaps inserted). existing->numseq + * is updated accordingly. + * + * existing: Must be a finalized alignment (ALN_STATUS_FINAL). + * new_seqs: Unaligned sequences to add. Modified in place (seq->seq gets gaps). + * n_threads: Number of threads for parallel alignment of new sequences. + * + * Returns OK on success, FAIL on error. */ +EXTERN int kalign_add_sequences(struct msa* existing, + struct msa* new_seqs, + int n_threads); + +#undef ALN_ADD_IMPORT +#undef EXTERN + +#endif diff --git a/lib/src/aln_apair_dist.c b/lib/src/aln_apair_dist.c index 42c12ce..5924c59 100644 --- a/lib/src/aln_apair_dist.c +++ b/lib/src/aln_apair_dist.c @@ -1,10 +1,43 @@ #include "tldevel.h" #include "msa_struct.h" +#ifdef HAVE_AVX2 +#include +#include +#endif + +#ifdef USE_THREADPOOL +#include "threadpool.h" +#endif + #define ALN_APAIR_DIST_IMPORT #include "aln_apair_dist.h" -static float pairwise_identity_dist(const char* a, const char* b, int alnlen); +float pairwise_identity_dist(const char* a, const char* b, int alnlen); + +#define REALIGN_NUM_ANCHORS 32 + +#ifdef USE_THREADPOOL +struct apair_ctx { + struct msa_seq** seqs; + float** dm; + int n; + int alnlen; +}; + +static void apair_row_fn(int row_start, int row_end, void *arg) +{ + struct apair_ctx *c = (struct apair_ctx *)arg; + for (int i = row_start; i < row_end; i++) { + const char *seq_i = c->seqs[i]->seq; + for (int j = i + 1; j < c->n; j++) { + float d = pairwise_identity_dist(seq_i, c->seqs[j]->seq, c->alnlen); + c->dm[i][j] = d; + c->dm[j][i] = d; + } + } +} +#endif int compute_aln_pairwise_dist(struct msa* msa, float*** dm_ptr) { @@ -26,6 +59,12 @@ int compute_aln_pairwise_dist(struct msa* msa, float*** dm_ptr) dm[i][i] = 0.0f; } +#ifdef USE_THREADPOOL + if(msa->pool && n >= KALIGN_DIST_MIN_SEQS){ + struct apair_ctx ctx = { msa->sequences, dm, n, msa->alnlen }; + tp_parallel_for_chunked(msa->pool, 0, n - 1, KALIGN_PFOR_MIN_CHUNK, apair_row_fn, &ctx); + }else{ +#endif for(i = 0; i < n - 1; i++){ const char* seq_i = msa->sequences[i]->seq; for(j = i + 1; j < n; j++){ @@ -35,6 +74,9 @@ int compute_aln_pairwise_dist(struct msa* msa, float*** dm_ptr) dm[j][i] = d; } } +#ifdef USE_THREADPOOL + } +#endif *dm_ptr = dm; return OK; @@ -60,8 +102,7 @@ void free_aln_dm(float** dm, int n) /* Distance = 1.0 - identity. Only counts columns where both sequences have a residue (no gap). */ -float pairwise_identity_dist(const char* a, const char* b, int alnlen) -{ +float pairwise_identity_dist(const char* a, const char* b, int alnlen) { int matches = 0; int aligned = 0; int i; @@ -80,3 +121,274 @@ float pairwise_identity_dist(const char* a, const char* b, int alnlen) } return 1.0f - (float)matches / (float)aligned; } + +/* ======================================================================== */ +/* Pairwise identity distance callback for bisecting k-means leaf clusters. */ +/* ======================================================================== */ + +float** aln_identity_pair_dist(struct msa* msa, int* samples, int n) +{ + float** dm = NULL; + int i, j; + + RUN(galloc(&dm, n, n)); + + for(i = 0; i < n; i++){ + dm[i][i] = 0.0f; + const char* seq_i = msa->sequences[samples[i]]->seq; + for(j = i + 1; j < n; j++){ + const char* seq_j = msa->sequences[samples[j]]->seq; + float d = pairwise_identity_dist(seq_i, seq_j, msa->alnlen); + /* Add small length-preference bonus matching d_estimation */ + int avg_len = (msa->sequences[samples[i]]->len + + msa->sequences[samples[j]]->len) / 2; + float add = (float)(avg_len < 10000 ? avg_len : 10000) / 10000.0f; + d += add; + dm[i][j] = d; + dm[j][i] = d; + } + } + + return dm; +ERROR: + return NULL; +} + +/* ======================================================================== */ +/* Anchor selection and N×K distances from aligned sequences. */ +/* Used by the realign loop to replace the O(N²) pairwise + O(N³) UPGMA */ +/* with O(N×K) distances + parallel bisecting k-means. */ +/* ======================================================================== */ + +#ifdef USE_THREADPOOL +struct anchor_aln_ctx { + struct msa_seq** seqs; + float* min_dist; + int anchor_idx; + int alnlen; +}; + +static void anchor_aln_init_fn(int start, int end, void* arg) +{ + struct anchor_aln_ctx* c = (struct anchor_aln_ctx*)arg; + const char* anchor_seq = c->seqs[c->anchor_idx]->seq; + for(int i = start; i < end; i++){ + c->min_dist[i] = pairwise_identity_dist( + c->seqs[i]->seq, anchor_seq, c->alnlen); + } +} + +static void anchor_aln_update_fn(int start, int end, void* arg) +{ + struct anchor_aln_ctx* c = (struct anchor_aln_ctx*)arg; + const char* anchor_seq = c->seqs[c->anchor_idx]->seq; + for(int i = start; i < end; i++){ + if(c->min_dist[i] < 0.0f) continue; + float d = pairwise_identity_dist( + c->seqs[i]->seq, anchor_seq, c->alnlen); + if(d < c->min_dist[i]){ + c->min_dist[i] = d; + } + } +} + +struct anchor_dm_ctx { + struct msa_seq** seqs; + int* anchors; + int K; + int alnlen; + float** dm; +}; + +static void anchor_dm_row_fn(int start, int end, void* arg) +{ + struct anchor_dm_ctx* c = (struct anchor_dm_ctx*)arg; + for(int i = start; i < end; i++){ + const char* seq_i = c->seqs[i]->seq; + for(int k = 0; k < c->K; k++){ + c->dm[i][k] = pairwise_identity_dist( + seq_i, c->seqs[c->anchors[k]]->seq, c->alnlen); + } + } +} +#endif + +int pick_anchor_from_alignment(struct msa* msa, int K, + int** anchors_out, int* K_out) +{ + int numseq = msa->numseq; + int* anchors = NULL; + float* min_dist = NULL; + int i, k; + + ASSERT(msa != NULL, "No MSA"); + ASSERT(msa->aligned == ALN_STATUS_FINAL, "MSA must be finalized"); + + if(K > numseq) K = numseq; + if(K < 1) K = 1; + + MMALLOC(anchors, sizeof(int) * K); + MMALLOC(min_dist, sizeof(float) * numseq); + + /* Pick first anchor: sequence closest to mean length */ + { + float mean_len = 0.0f; + float best_diff = 1e30f; + int best_idx = 0; + for(i = 0; i < numseq; i++){ + mean_len += (float)msa->sequences[i]->len; + } + mean_len /= (float)numseq; + for(i = 0; i < numseq; i++){ + float diff = (float)msa->sequences[i]->len - mean_len; + if(diff < 0) diff = -diff; + if(diff < best_diff){ + best_diff = diff; + best_idx = i; + } + } + anchors[0] = best_idx; + } + + /* Initialize min_dist: identity distance from each seq to first anchor */ +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct anchor_aln_ctx ctx = { msa->sequences, min_dist, + anchors[0], msa->alnlen }; + tp_parallel_for_chunked(msa->pool, 0, numseq, + KALIGN_PFOR_MIN_CHUNK, + anchor_aln_init_fn, &ctx); + }else{ +#endif + for(i = 0; i < numseq; i++){ + min_dist[i] = pairwise_identity_dist( + msa->sequences[i]->seq, + msa->sequences[anchors[0]]->seq, + msa->alnlen); + } +#ifdef USE_THREADPOOL + } +#endif + min_dist[anchors[0]] = -1.0f; + + /* Farthest-first: pick remaining K-1 anchors */ + for(k = 1; k < K; k++){ + float best_min = -1.0f; + int best_idx = 0; + for(i = 0; i < numseq; i++){ + if(min_dist[i] > best_min){ + best_min = min_dist[i]; + best_idx = i; + } + } + anchors[k] = best_idx; + min_dist[best_idx] = -1.0f; + + /* Update min_dist with new anchor */ +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct anchor_aln_ctx ctx = { msa->sequences, min_dist, + best_idx, msa->alnlen }; + tp_parallel_for_chunked(msa->pool, 0, numseq, + KALIGN_PFOR_MIN_CHUNK, + anchor_aln_update_fn, &ctx); + }else{ +#endif + for(i = 0; i < numseq; i++){ + if(min_dist[i] < 0.0f) continue; + float d = pairwise_identity_dist( + msa->sequences[i]->seq, + msa->sequences[best_idx]->seq, + msa->alnlen); + if(d < min_dist[i]){ + min_dist[i] = d; + } + } +#ifdef USE_THREADPOOL + } +#endif + } + + MFREE(min_dist); + *anchors_out = anchors; + *K_out = K; + return OK; +ERROR: + if(anchors) MFREE(anchors); + if(min_dist) MFREE(min_dist); + return FAIL; +} + +int compute_aln_anchor_dist(struct msa* msa, int* anchors, int K, + float*** dm_out) +{ + float** dm = NULL; + int numseq = msa->numseq; + int i, j; + int padded_K; + + ASSERT(msa != NULL, "No MSA"); + ASSERT(msa->aligned == ALN_STATUS_FINAL, "MSA must be finalized"); + ASSERT(anchors != NULL, "No anchors"); + ASSERT(K > 0, "K must be > 0"); + + /* Pad K to multiple of 8 for AVX2 alignment (matches d_estimation) */ + padded_K = K / 8; + if(K % 8) padded_K++; + padded_K <<= 3; + + MMALLOC(dm, sizeof(float*) * numseq); + for(i = 0; i < numseq; i++){ + dm[i] = NULL; + } + for(i = 0; i < numseq; i++){ +#ifdef HAVE_AVX2 + dm[i] = _mm_malloc(sizeof(float) * padded_K, 32); + if(!dm[i]) goto ERROR; +#else + MMALLOC(dm[i], sizeof(float) * padded_K); +#endif + for(j = 0; j < padded_K; j++){ + dm[i][j] = 0.0f; + } + } + + /* Fill N×K: identity distance from each seq to each anchor */ +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct anchor_dm_ctx ctx = { msa->sequences, anchors, K, + msa->alnlen, dm }; + tp_parallel_for_chunked(msa->pool, 0, numseq, + KALIGN_PFOR_MIN_CHUNK, + anchor_dm_row_fn, &ctx); + }else{ +#endif + for(i = 0; i < numseq; i++){ + const char* seq_i = msa->sequences[i]->seq; + for(j = 0; j < K; j++){ + dm[i][j] = pairwise_identity_dist( + seq_i, msa->sequences[anchors[j]]->seq, + msa->alnlen); + } + } +#ifdef USE_THREADPOOL + } +#endif + + *dm_out = dm; + return OK; +ERROR: + if(dm){ + for(i = 0; i < numseq; i++){ + if(dm[i]){ +#ifdef HAVE_AVX2 + _mm_free(dm[i]); +#else + MFREE(dm[i]); +#endif + } + } + MFREE(dm); + } + return FAIL; +} diff --git a/lib/src/aln_apair_dist.h b/lib/src/aln_apair_dist.h index fa73a6a..160447d 100644 --- a/lib/src/aln_apair_dist.h +++ b/lib/src/aln_apair_dist.h @@ -11,17 +11,39 @@ #endif #endif +#define REALIGN_NUM_ANCHORS 32 + struct msa; /* Compute NxN pairwise identity distances from an aligned MSA. Distance = 1.0 - (matches / aligned_positions) for each pair. The MSA must be finalized (sequences contain gap characters). - Caller must free the returned matrix with free_dm(). */ + Caller must free the returned matrix with free_aln_dm(). */ EXTERN int compute_aln_pairwise_dist(struct msa* msa, float*** dm_ptr); /* Free an NxN distance matrix allocated by compute_aln_pairwise_dist. */ EXTERN void free_aln_dm(float** dm, int n); +/* Identity distance between two aligned sequences (0=identical, 1=no matches). + Only counts columns where both have a residue (no gap). */ +EXTERN float pairwise_identity_dist(const char* a, const char* b, int alnlen); + +/* Pairwise identity distance callback for bisecting k-means leaf clusters. + Returns an n×n distance matrix (allocated with galloc, freed by gfree). + Sequences must be finalized (seq->seq has gap characters, msa->alnlen set). */ +EXTERN float** aln_identity_pair_dist(struct msa* msa, int* samples, int n); + +/* Select K diverse anchors from a finalized alignment using farthest-first + traversal with identity distance. Returns anchor indices and actual K. */ +EXTERN int pick_anchor_from_alignment(struct msa* msa, int K, + int** anchors_out, int* K_out); + +/* Compute N×K identity distances from each sequence to K anchors. + Output format matches d_estimation(pair=0): rows AVX2-padded. + The MSA must be finalized. Caller frees with free_aln_anchor_dm(). */ +EXTERN int compute_aln_anchor_dist(struct msa* msa, int* anchors, int K, + float*** dm_out); + #undef ALN_APAIR_DIST_IMPORT #undef EXTERN diff --git a/lib/src/aln_controller.c b/lib/src/aln_controller.c index 582d27a..b748c3f 100644 --- a/lib/src/aln_controller.c +++ b/lib/src/aln_controller.c @@ -2,8 +2,6 @@ #include "tldevel.h" - - #include "aln_param.h" #include "aln_struct.h" @@ -12,10 +10,20 @@ #include "aln_seqprofile.h" #include "aln_profileprofile.h" +#ifdef USE_THREADPOOL +#include "threadpool.h" + +static void wrap_seqseq_fwd(void *arg) { aln_seqseq_foward((struct aln_mem *)arg); } +static void wrap_seqseq_bwd(void *arg) { aln_seqseq_backward((struct aln_mem *)arg); } +static void wrap_profprof_fwd(void *arg) { aln_profileprofile_foward((struct aln_mem *)arg); } +static void wrap_profprof_bwd(void *arg) { aln_profileprofile_backward((struct aln_mem *)arg); } +static void wrap_seqprof_fwd(void *arg) { aln_seqprofile_foward((struct aln_mem *)arg); } +static void wrap_seqprof_bwd(void *arg) { aln_seqprofile_backward((struct aln_mem *)arg); } +#endif + #define ALN_CONTROLLER_IMPORT #include "aln_controller.h" - static int aln_continue(struct aln_mem* m,float input_states[],int old_cor[],int meet,int transition, uint8_t serial); int aln_runner(struct aln_mem* m) @@ -61,6 +69,42 @@ int aln_runner(struct aln_mem* m) m->enda_2 = old_cor[1]; /* fprintf(stderr,"Forward:%d-%d %d-%d\n",m->starta,m->enda,m->startb,m->endb); */ +#ifdef USE_THREADPOOL + if(m->run_parallel && m->pool){ + tp_group_t *g = tp_group_create(m->pool); + if(m->seq1){ + tp_group_submit(g, wrap_seqseq_fwd, m); + tp_group_submit(g, wrap_seqseq_bwd, m); + tp_group_wait(g); + aln_seqseq_meetup(m,old_cor,&meet,&transition,&score); + }else if(m->prof2){ + tp_group_submit(g, wrap_profprof_fwd, m); + tp_group_submit(g, wrap_profprof_bwd, m); + tp_group_wait(g); + aln_profileprofile_meetup(m,old_cor,&meet,&transition,&score); + }else{ + tp_group_submit(g, wrap_seqprof_fwd, m); + tp_group_submit(g, wrap_seqprof_bwd, m); + tp_group_wait(g); + aln_seqprofile_meetup(m,old_cor,&meet,&transition,&score); + } + tp_group_destroy(g); + }else{ + if(m->seq1){ + aln_seqseq_foward(m); + aln_seqseq_backward(m); + aln_seqseq_meetup(m,old_cor,&meet,&transition,&score); + }else if(m->prof2){ + aln_profileprofile_foward(m); + aln_profileprofile_backward(m); + aln_profileprofile_meetup(m,old_cor,&meet,&transition,&score); + }else{ + aln_seqprofile_foward(m); + aln_seqprofile_backward(m); + aln_seqprofile_meetup(m,old_cor,&meet,&transition,&score); + } + } +#else #ifdef HAVE_OPENMP #pragma omp parallel #pragma omp single nowait @@ -71,7 +115,6 @@ int aln_runner(struct aln_mem* m) #pragma omp task shared(m) if(m->run_parallel) #endif aln_seqseq_foward(m); - #ifdef HAVE_OPENMP #pragma omp task shared(m) if(m->run_parallel) #endif @@ -109,6 +152,7 @@ int aln_runner(struct aln_mem* m) } #ifdef HAVE_OPENMP } +#endif #endif if(m->mode == ALN_MODE_SCORE_ONLY){ diff --git a/lib/src/aln_mem.c b/lib/src/aln_mem.c index 49a0d0b..65257c9 100644 --- a/lib/src/aln_mem.c +++ b/lib/src/aln_mem.c @@ -35,10 +35,13 @@ int alloc_aln_mem(struct aln_mem** mem, int x) m->flip_bit_map = NULL; m->flip_n_targets = 0; m->flip_n_uncertain = 0; + m->run_parallel = 0; +#ifdef USE_THREADPOOL + m->pool = NULL; +#endif m->ap = NULL; m->consistency = NULL; - m->consistency_stride = 0; m->starta = 0; m->startb = 0; diff --git a/lib/src/aln_param.c b/lib/src/aln_param.c index b9f3b43..c219e67 100644 --- a/lib/src/aln_param.c +++ b/lib/src/aln_param.c @@ -13,6 +13,8 @@ static int set_subm_gaps_PFASUM60(struct aln_param *ap); static int set_subm_gaps_DNA(struct aln_param *ap); static int set_subm_gaps_DNA_internal(struct aln_param *ap); static int set_subm_gaps_RNA(struct aln_param *ap); +static int set_subm_gaps_nuc_kimura(struct aln_param *ap, float match, + float transition, float transversion); int aln_param_init(struct aln_param **aln_param,int biotype , int n_threads, int type, float gpo, float gpe, float tgpe) { @@ -33,19 +35,29 @@ int aln_param_init(struct aln_param **aln_param,int biotype , int n_threads, int } } if(biotype == ALN_BIOTYPE_DNA){ - /* include/kalign/ */ switch (type) { - case KALIGN_TYPE_DNA: + case KALIGN_MATRIX_DNA: set_subm_gaps_DNA(ap); break; - case KALIGN_TYPE_DNA_INTERNAL: + case KALIGN_MATRIX_DNA_INTERNAL: set_subm_gaps_DNA_internal(ap); break; - case KALIGN_TYPE_RNA: + case KALIGN_MATRIX_RNA: set_subm_gaps_RNA(ap); break; - case KALIGN_TYPE_PROTEIN: - ERROR_MSG("Detected DNA sequences but --type protein option was selected."); + case KALIGN_MATRIX_NUC_1PAM: + set_subm_gaps_nuc_kimura(ap, 5.0f, -14.2f, -16.8f); + break; + case KALIGN_MATRIX_NUC_20PAM: + set_subm_gaps_nuc_kimura(ap, 5.0f, -4.6f, -7.2f); + break; + case KALIGN_MATRIX_NUC_200PAM: + set_subm_gaps_nuc_kimura(ap, 5.0f, 0.8f, -3.4f); + break; + case KALIGN_MATRIX_PFASUM43: + case KALIGN_MATRIX_PFASUM60: + case KALIGN_MATRIX_CORBLOSUM66: + ERROR_MSG("Detected DNA sequences but a protein matrix was selected."); break; default: set_subm_gaps_RNA(ap); @@ -53,26 +65,23 @@ int aln_param_init(struct aln_param **aln_param,int biotype , int n_threads, int } }else if(biotype == ALN_BIOTYPE_PROTEIN){ switch (type) { - case KALIGN_TYPE_PROTEIN: + case KALIGN_MATRIX_PFASUM43: set_subm_gaps_PFASUM43(ap); break; - case KALIGN_TYPE_PROTEIN_DIVERGENT: - set_subm_gaps_gon250(ap); - break; - case KALIGN_TYPE_PROTEIN_PFASUM43: - set_subm_gaps_PFASUM43(ap); - break; - case KALIGN_TYPE_PROTEIN_PFASUM60: + case KALIGN_MATRIX_PFASUM60: set_subm_gaps_PFASUM60(ap); break; - case KALIGN_TYPE_DNA: - ERROR_MSG("Detected protein sequences but --type dna option was selected."); + case KALIGN_MATRIX_CORBLOSUM66: + set_subm_gaps_CorBLOSUM66_13plus(ap); break; - case KALIGN_TYPE_DNA_INTERNAL: - ERROR_MSG("Detected protein sequences but --type internal option was selected."); + case KALIGN_TYPE_PROTEIN_DIVERGENT: + /* Legacy GONNET path — retained for backward compat */ + set_subm_gaps_gon250(ap); break; - case KALIGN_TYPE_RNA: - ERROR_MSG("Detected protein sequences but --type rna option was selected."); + case KALIGN_MATRIX_DNA: + case KALIGN_MATRIX_DNA_INTERNAL: + case KALIGN_MATRIX_RNA: + ERROR_MSG("Detected protein sequences but a nucleotide matrix was selected."); break; default: set_subm_gaps_PFASUM43(ap); @@ -362,6 +371,33 @@ int set_subm_gaps_RNA(struct aln_param* ap) return OK; } +/* Kimura two-parameter nucleotide matrix. + Alphabet: A=0, C=1, G=2, T/U=3, N=4. + Transitions: A<->G, C<->T. Transversions: all others. + Gap penalties use the same defaults as the DNA matrix. */ +int set_subm_gaps_nuc_kimura(struct aln_param *ap, float match, + float transition, float transversion) +{ + int i, j; + /* Transition pairs: (0,2)=A-G, (2,0)=G-A, (1,3)=C-T, (3,1)=T-C */ + for(i = 0; i < 5; i++){ + for(j = 0; j < 5; j++){ + if(i == j){ + ap->subm[i][j] = match; + }else if((i == 0 && j == 2) || (i == 2 && j == 0) || + (i == 1 && j == 3) || (i == 3 && j == 1)){ + ap->subm[i][j] = transition; + }else{ + ap->subm[i][j] = transversion; + } + } + } + ap->gpo = 8.0f; + ap->gpe = 6.0f; + ap->tgpe = 0.0f; + return OK; +} + void aln_param_free(struct aln_param *ap) { if(ap){ diff --git a/lib/src/aln_param.h b/lib/src/aln_param.h index 25e654b..dba3219 100644 --- a/lib/src/aln_param.h +++ b/lib/src/aln_param.h @@ -11,19 +11,20 @@ #endif #endif -/* #define KALIGN_DNA 0 */ -/* #define KALIGN_DNA_INTERNAL 1 */ -/* #define KALIGN_RNA 2 */ -/* #define KALIGN_PROTEIN 3 */ +#ifdef USE_THREADPOOL +typedef struct threadpool threadpool_t; +#endif struct aln_param{ int nthreads; +#ifdef USE_THREADPOOL + threadpool_t *pool; /* shared pool for all parallel work */ +#endif /* actual parameters */ float** subm; float gpo; float gpe; float tgpe; - float score; float dist_scale; /* distance-dependent gap scaling: 0=off, >0 scales gap penalties down for divergent pairs */ float vsm_amax; /* variable scoring matrix: 0=off, >0 subtracts a(d)=max(0,amax-d) from subm scores */ float subm_offset; /* computed per alignment step: amount to subtract from substitution scores */ diff --git a/lib/src/aln_profileprofile.c b/lib/src/aln_profileprofile.c index 2b0acc0..d41d948 100644 --- a/lib/src/aln_profileprofile.c +++ b/lib/src/aln_profileprofile.c @@ -6,6 +6,7 @@ #include "aln_param.h" #include "aln_struct.h" +#include "anchor_consistency.h" #define ALN_PROFILEPROFILE_IMPORT #include "aln_profileprofile.h" @@ -106,7 +107,7 @@ int aln_profileprofile_foward(struct aln_mem* m) } prof2 -= 32; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -136,7 +137,7 @@ int aln_profileprofile_foward(struct aln_mem* m) } prof2 -= 32; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -249,7 +250,7 @@ int aln_profileprofile_backward(struct aln_mem* m) } prof2 -= 32; if(m->consistency){ - pa += m->consistency[(m->starta_2 + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, m->starta_2 + i, j); } s[j].a = pa; @@ -278,7 +279,7 @@ int aln_profileprofile_backward(struct aln_mem* m) } prof2 -= 32; if(m->consistency){ - pa += m->consistency[(m->starta_2 + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, m->starta_2 + i, j); } s[j].a = pa; diff --git a/lib/src/aln_refine.c b/lib/src/aln_refine.c index 794e64d..64cbe02 100644 --- a/lib/src/aln_refine.c +++ b/lib/src/aln_refine.c @@ -149,7 +149,6 @@ int refine_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int /* Compute consistency bonus for all merge types */ ml->consistency = NULL; - ml->consistency_stride = 0; { struct consistency_table* ct = (struct consistency_table*)msa->consistency_table; if(ct != NULL){ @@ -180,7 +179,6 @@ int refine_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int RUN(anchor_consistency_get_bonus_profile(ct, msa, dp_row_node, dp_rows, dp_col_node, dp_cols, &ml->consistency)); - ml->consistency_stride = dp_cols; } } @@ -288,9 +286,8 @@ int refine_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int /* Free consistency bonus */ if(ml->consistency){ - MFREE(ml->consistency); + sparse_bonus_free(ml->consistency); ml->consistency = NULL; - ml->consistency_stride = 0; } /* Update confidence from best trial */ @@ -403,7 +400,6 @@ int replay_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int /* Compute consistency bonus for all merge types */ ml->consistency = NULL; - ml->consistency_stride = 0; { struct consistency_table* ct = (struct consistency_table*)msa->consistency_table; if(ct != NULL){ @@ -434,7 +430,6 @@ int replay_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int RUN(anchor_consistency_get_bonus_profile(ct, msa, dp_row_node, dp_rows, dp_col_node, dp_cols, &ml->consistency)); - ml->consistency_stride = dp_cols; } } @@ -442,9 +437,8 @@ int replay_edge(struct msa* msa, struct aln_param* ap, struct aln_tasks* t, int /* Free consistency bonus */ if(ml->consistency){ - MFREE(ml->consistency); + sparse_bonus_free(ml->consistency); ml->consistency = NULL; - ml->consistency_stride = 0; } /* Store alignment confidence */ diff --git a/lib/src/aln_run.c b/lib/src/aln_run.c index 335ae1a..cde6148 100644 --- a/lib/src/aln_run.c +++ b/lib/src/aln_run.c @@ -6,6 +6,9 @@ #ifdef HAVE_OPENMP #include #endif +#ifdef USE_THREADPOOL +#include "threadpool.h" +#endif #include "task.h" @@ -20,6 +23,7 @@ /* #include "weave_alignment.h" */ #include "sp_score.h" #include "anchor_consistency.h" +#include "msa_consistency.h" #include #include @@ -40,6 +44,37 @@ static void recursive_aln_inline(struct msa* msa, struct aln_tasks*t, struct aln /* static int SampleWithoutReplacement(struct rng_state* rng, int N, int n,int* samples); */ /* static int int_cmp(const void *a, const void *b); */ +#ifdef USE_THREADPOOL +struct recursive_aln_arg { + struct msa *msa; + struct aln_tasks *t; + struct aln_param *ap; + uint8_t *active; + int c; +}; + +static void recursive_aln_task(void *arg) +{ + struct recursive_aln_arg *a = (struct recursive_aln_arg *)arg; + recursive_aln(a->msa, a->t, a->ap, a->active, a->c); +} + +struct recursive_aln_inline_arg { + struct msa *msa; + struct aln_tasks *t; + struct aln_param *ap; + uint8_t *active; + int c; + int n_trials; +}; + +static void recursive_aln_inline_task(void *arg) +{ + struct recursive_aln_inline_arg *a = (struct recursive_aln_inline_arg *)arg; + recursive_aln_inline(a->msa, a->t, a->ap, a->active, a->c, a->n_trials); +} +#endif + int create_msa_tree(struct msa* msa, struct aln_param* ap,struct aln_tasks* t) { int i; @@ -62,9 +97,11 @@ int create_msa_tree(struct msa* msa, struct aln_param* ap,struct aln_tasks* t) msa->run_parallel = 0; } +#if !defined(USE_THREADPOOL) #ifdef HAVE_OPENMP #pragma omp parallel #pragma omp single nowait +#endif #endif recursive_aln(msa, t, ap, active, t->n_tasks-1); @@ -92,6 +129,19 @@ void recursive_aln(struct msa* msa, struct aln_tasks*t, struct aln_param* ap, ui a = local_t->a - msa->numseq; b = local_t->b - msa->numseq; +#ifdef USE_THREADPOOL + { + struct recursive_aln_arg arg_a = { msa, t, ap, active, a }; + struct recursive_aln_arg arg_b = { msa, t, ap, active, b }; + tp_group_t *g = tp_group_create(ap->pool); + if(!active[local_t->a] && local_t->a >= msa->numseq) + tp_group_submit(g, recursive_aln_task, &arg_a); + if(!active[local_t->b] && local_t->b >= msa->numseq) + tp_group_submit(g, recursive_aln_task, &arg_b); + tp_group_wait(g); + tp_group_destroy(g); + } +#else if(!active[local_t->a] && local_t->a >= msa->numseq){ #ifdef HAVE_OPENMP #pragma omp task shared(msa,t,ap,active) firstprivate(a) @@ -106,6 +156,7 @@ void recursive_aln(struct msa* msa, struct aln_tasks*t, struct aln_param* ap, ui } #ifdef HAVE_OPENMP #pragma omp taskwait +#endif #endif struct aln_mem* ml = NULL; @@ -114,6 +165,10 @@ void recursive_aln(struct msa* msa, struct aln_tasks*t, struct aln_param* ap, ui ml->ap = ap; ml->mode = ALN_MODE_FULL; + ml->run_parallel = msa->run_parallel; +#ifdef USE_THREADPOOL + ml->pool = ap->pool; +#endif do_align(msa,t,ml,c); active[local_t->a] = 0; @@ -257,13 +312,14 @@ int do_align(struct msa* msa,struct aln_tasks* t,struct aln_mem* m, int task_id) m->margin_sum = 0.0F; m->margin_count = 0; m->consistency = NULL; - m->consistency_stride = 0; /* Compute consistency bonus for all merge types */ { - struct consistency_table* ct = (struct consistency_table*)msa->consistency_table; - if(ct != NULL){ - int dp_row_node, dp_col_node, dp_rows, dp_cols; + int dp_row_node, dp_col_node, dp_rows, dp_cols; + int have_consistency = (msa->poar_consistency != NULL) + || (msa->consistency_table != NULL); + + if(have_consistency){ if(msa->nsip[a] == 1 && msa->nsip[b] == 1){ if(m->len_a < m->len_b){ dp_row_node = a; dp_rows = m->len_a; @@ -287,10 +343,18 @@ int do_align(struct msa* msa,struct aln_tasks* t,struct aln_mem* m, int task_id) dp_col_node = a; dp_cols = m->len_a; } } - RUN(anchor_consistency_get_bonus_profile(ct, msa, - dp_row_node, dp_rows, dp_col_node, dp_cols, - &m->consistency)); - m->consistency_stride = dp_cols; + + if(msa->poar_consistency != NULL){ + RUN(poar_consistency_get_bonus( + (struct poar_consistency_ctx*)msa->poar_consistency, + msa, dp_row_node, dp_rows, dp_col_node, dp_cols, + &m->consistency)); + }else{ + RUN(anchor_consistency_get_bonus_profile( + (struct consistency_table*)msa->consistency_table, + msa, dp_row_node, dp_rows, dp_col_node, dp_cols, + &m->consistency)); + } } } @@ -399,9 +463,8 @@ int do_align(struct msa* msa,struct aln_tasks* t,struct aln_mem* m, int task_id) /* Free consistency bonus if allocated */ if(m->consistency){ - MFREE(m->consistency); + sparse_bonus_free(m->consistency); m->consistency = NULL; - m->consistency_stride = 0; } /* Restore original aln_param for profile update (unscaled base penalties) */ @@ -462,9 +525,20 @@ int create_msa_tree_inline_refine(struct msa* msa, struct aln_param* ap, active[i] = 0; } - /* Inline refine is sequential — multi-trial per edge isn't thread-safe */ - msa->run_parallel = 0; + /* Tree-level fork-join is safe (sibling edges are independent). + Multi-trial loop within each edge stays serial. + Hirschberg fwd/bwd parallelism enabled via run_parallel. */ + msa->run_parallel = 1; + if(ap->nthreads == 1){ + msa->run_parallel = 0; + } +#if !defined(USE_THREADPOOL) +#ifdef HAVE_OPENMP +#pragma omp parallel +#pragma omp single nowait +#endif +#endif recursive_aln_inline(msa, t, ap, active, t->n_tasks - 1, n_trials); MFREE(active); @@ -487,17 +561,46 @@ void recursive_aln_inline(struct msa* msa, struct aln_tasks* t, a = local_t->a - msa->numseq; b = local_t->b - msa->numseq; + /* Fork-join: sibling subtrees are independent — same pattern as recursive_aln */ +#ifdef USE_THREADPOOL + { + struct recursive_aln_inline_arg arg_a = { msa, t, ap, active, a, n_trials }; + struct recursive_aln_inline_arg arg_b = { msa, t, ap, active, b, n_trials }; + tp_group_t *g = tp_group_create(ap->pool); + if(!active[local_t->a] && local_t->a >= msa->numseq) + tp_group_submit(g, recursive_aln_inline_task, &arg_a); + if(!active[local_t->b] && local_t->b >= msa->numseq) + tp_group_submit(g, recursive_aln_inline_task, &arg_b); + tp_group_wait(g); + tp_group_destroy(g); + } +#else if(!active[local_t->a] && local_t->a >= msa->numseq){ +#ifdef HAVE_OPENMP +#pragma omp task shared(msa,t,ap,active) firstprivate(a,n_trials) +#endif recursive_aln_inline(msa, t, ap, active, a, n_trials); } if(!active[local_t->b] && local_t->b >= msa->numseq){ +#ifdef HAVE_OPENMP +#pragma omp task shared(msa,t,ap,active) firstprivate(b,n_trials) +#endif recursive_aln_inline(msa, t, ap, active, b, n_trials); } +#ifdef HAVE_OPENMP +#pragma omp taskwait +#endif +#endif + /* After children complete: align this edge (multi-trial stays serial) */ struct aln_mem* ml = NULL; alloc_aln_mem(&ml, 256); ml->ap = ap; ml->mode = ALN_MODE_FULL; + ml->run_parallel = msa->run_parallel; +#ifdef USE_THREADPOOL + ml->pool = ap->pool; +#endif do_align_inline_refine(msa, t, ml, c, n_trials); @@ -564,11 +667,12 @@ int do_align_inline_refine(struct msa* msa, struct aln_tasks* t, /* Compute consistency bonus for all merge types */ m->consistency = NULL; - m->consistency_stride = 0; { - struct consistency_table* ct = (struct consistency_table*)msa->consistency_table; - if(ct != NULL){ - int dp_row_node, dp_col_node, dp_rows, dp_cols; + int dp_row_node, dp_col_node, dp_rows, dp_cols; + int have_consistency = (msa->poar_consistency != NULL) + || (msa->consistency_table != NULL); + + if(have_consistency){ if(msa->nsip[a] == 1 && msa->nsip[b] == 1){ if(len_a < len_b){ dp_row_node = a; dp_rows = len_a; @@ -592,10 +696,18 @@ int do_align_inline_refine(struct msa* msa, struct aln_tasks* t, dp_col_node = a; dp_cols = len_a; } } - RUN(anchor_consistency_get_bonus_profile(ct, msa, - dp_row_node, dp_rows, dp_col_node, dp_cols, - &m->consistency)); - m->consistency_stride = dp_cols; + + if(msa->poar_consistency != NULL){ + RUN(poar_consistency_get_bonus( + (struct poar_consistency_ctx*)msa->poar_consistency, + msa, dp_row_node, dp_rows, dp_col_node, dp_cols, + &m->consistency)); + }else{ + RUN(anchor_consistency_get_bonus_profile( + (struct consistency_table*)msa->consistency_table, + msa, dp_row_node, dp_rows, dp_col_node, dp_cols, + &m->consistency)); + } } } @@ -734,9 +846,8 @@ int do_align_inline_refine(struct msa* msa, struct aln_tasks* t, /* Free consistency bonus if allocated */ if(m->consistency){ - MFREE(m->consistency); + sparse_bonus_free(m->consistency); m->consistency = NULL; - m->consistency_stride = 0; } /* Store confidence */ diff --git a/lib/src/aln_seqprofile.c b/lib/src/aln_seqprofile.c index 1a58962..3b8cfe0 100644 --- a/lib/src/aln_seqprofile.c +++ b/lib/src/aln_seqprofile.c @@ -5,6 +5,7 @@ #include "aln_param.h" #include "aln_struct.h" +#include "anchor_consistency.h" #define ALN_SEQPROFILE_IMPORT #include "aln_seqprofile.h" #define MAX(a, b) (a > b ? a : b) @@ -80,7 +81,7 @@ int aln_seqprofile_foward(struct aln_mem* m) pa += prof1[32 + seq2[j]]; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -105,7 +106,7 @@ int aln_seqprofile_foward(struct aln_mem* m) pa += prof1[32 + seq2[j]]; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -191,7 +192,7 @@ int aln_seqprofile_backward(struct aln_mem* m) pa = MAX3(pa,pga - open,pgb +prof1[91]); pa += prof1[32 + seq2[j]]; if(m->consistency){ - pa += m->consistency[(m->starta_2 + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, m->starta_2 + i, j); } s[j].a = pa; @@ -214,7 +215,7 @@ int aln_seqprofile_backward(struct aln_mem* m) pa = MAX3(pa,pga - open,pgb +prof1[91]); pa += prof1[32 + seq2[j]]; if(m->consistency){ - pa += m->consistency[(m->starta_2 + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, m->starta_2 + i, j); } s[j].a = pa; diff --git a/lib/src/aln_seqseq.c b/lib/src/aln_seqseq.c index d74b70b..eabb516 100644 --- a/lib/src/aln_seqseq.c +++ b/lib/src/aln_seqseq.c @@ -6,6 +6,7 @@ #include "aln_param.h" #include "aln_struct.h" +#include "anchor_consistency.h" #define ALN_SEQSEQ_IMPORT #include "aln_seqseq.h" @@ -81,7 +82,7 @@ int aln_seqseq_foward(struct aln_mem* m) pa = MAX3(pa,pga-gpo,pgb-gpo); pa += subp[seq2[j]] - soff; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -103,7 +104,7 @@ int aln_seqseq_foward(struct aln_mem* m) pa = MAX3(pa,pga-gpo,pgb-gpo); pa += subp[seq2[j]] - soff; if(m->consistency){ - pa += m->consistency[i * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, i, j); } s[j].a = pa; @@ -197,7 +198,7 @@ int aln_seqseq_backward(struct aln_mem* m) pa += subp[seq2[j]] - soff; if(m->consistency){ - pa += m->consistency[(starta + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, starta + i, j); } s[j].a = pa; @@ -221,7 +222,7 @@ int aln_seqseq_backward(struct aln_mem* m) pa += subp[seq2[j]] - soff; if(m->consistency){ - pa += m->consistency[(starta + i) * m->consistency_stride + j]; + pa += sparse_bonus_lookup(m->consistency, starta + i, j); } s[j].a = pa; diff --git a/lib/src/aln_struct.h b/lib/src/aln_struct.h index 26c7128..f3e3ac1 100644 --- a/lib/src/aln_struct.h +++ b/lib/src/aln_struct.h @@ -3,9 +3,15 @@ #include +#ifdef USE_THREADPOOL +typedef struct threadpool threadpool_t; +#endif + #define ALN_MODE_SCORE_ONLY 2 #define ALN_MODE_FULL 1 +struct sparse_bonus; + struct states{ float a; float ga; @@ -24,6 +30,9 @@ struct aln_mem{ int* path; int* tmp_path; uint8_t run_parallel; +#ifdef USE_THREADPOOL + threadpool_t *pool; +#endif int alloc_path_len; float score; float margin_sum; /* accumulated meetup margins */ @@ -54,8 +63,7 @@ struct aln_mem{ int sip; int mode; - float* consistency; /* bonus matrix [i * consistency_stride + j], NULL if disabled */ - int consistency_stride; /* = len_b (stride for j dimension) */ + struct sparse_bonus* consistency; /* sparse bonus matrix, NULL if disabled */ }; #endif diff --git a/lib/src/aln_wrap.c b/lib/src/aln_wrap.c index a051fb5..93ac65a 100644 --- a/lib/src/aln_wrap.c +++ b/lib/src/aln_wrap.c @@ -1,8 +1,14 @@ #include "tldevel.h" #include "tlmisc.h" +#include #include "esl_stopwatch.h" #include "task.h" #include "msa_struct.h" + +#ifdef HAVE_AVX2 +#include +#include +#endif #include "msa_op.h" #include "msa_alloc.h" #include "msa_check.h" @@ -18,31 +24,41 @@ #include "aln_apair_dist.h" #include "anchor_consistency.h" #include "kalign/kalign.h" +#include "kalign/kalign_config.h" +#include "ensemble.h" #ifdef HAVE_OPENMP #include #endif +#ifdef USE_THREADPOOL +#include "threadpool.h" +#endif + #define ALN_WRAP_IMPORT #include "aln_wrap.h" -/* Resolve PFASUM_AUTO: pick PFASUM43 for divergent, PFASUM60 for closer. - Must be called after build_tree_kmeans which fills msa->seq_distances[]. */ -static int resolve_pfasum_auto(struct msa *msa, int *type) +/* Resolve KALIGN_MATRIX_AUTO into a concrete matrix constant. + For protein: use length-ratio heuristic (PFASUM43 vs PFASUM60). + For DNA/RNA: pick the standard matrix for that biotype. */ +static int resolve_matrix_auto(struct msa *msa, int *type) { int i; int min_len, max_len; float len_ratio; - if(*type != KALIGN_TYPE_PROTEIN_PFASUM_AUTO){ + if(*type != KALIGN_MATRIX_AUTO){ return OK; } - if(msa->biotype != ALN_BIOTYPE_PROTEIN){ - *type = KALIGN_TYPE_PROTEIN_PFASUM43; + + if(msa->biotype == ALN_BIOTYPE_DNA){ + /* DNA biotype covers both DNA and RNA sequences. + Default to RNA matrix (broader scoring range). */ + *type = KALIGN_MATRIX_RNA; return OK; } - /* Use sequence length ratio (max/min) to select matrix. + /* Protein: use sequence length ratio (max/min) to select matrix. Similar-length sequences (ratio < 1.5) prefer PFASUM43; high length variation (insertions/extensions) prefers PFASUM60. */ min_len = msa->sequences[0]->len; @@ -55,14 +71,14 @@ static int resolve_pfasum_auto(struct msa *msa, int *type) len_ratio = (min_len > 0) ? (float)max_len / (float)min_len : 1.0f; if(len_ratio < 1.5f){ - *type = KALIGN_TYPE_PROTEIN_PFASUM43; + *type = KALIGN_MATRIX_PFASUM43; }else{ - *type = KALIGN_TYPE_PROTEIN_PFASUM60; + *type = KALIGN_MATRIX_PFASUM60; } if(!msa->quiet){ LOG_MSG("Auto matrix: len_ratio=%.2f -> %s", len_ratio, - *type == KALIGN_TYPE_PROTEIN_PFASUM60 ? "PFASUM60" : "PFASUM43"); + *type == KALIGN_MATRIX_PFASUM60 ? "PFASUM60" : "PFASUM43"); } return OK; } @@ -107,50 +123,34 @@ static int compute_tree_weights(struct msa* msa, struct aln_tasks* tasks) return FAIL; } -int kalign(char **seq, int *len, int numseq,int n_threads, int type, float gpo, float gpe, float tgpe, char ***aligned, int *out_aln_len) -{ - struct msa *msa = NULL; - RUN(kalign_arr_to_msa(seq, len,numseq, &msa)); - - msa->quiet = 1; - if(n_threads < 1){ - n_threads = 1; - } - RUN(kalign_run(msa,n_threads, type, gpo, gpe, tgpe, KALIGN_REFINE_NONE, 0)); - - RUN(kalign_msa_to_arr(msa, aligned, out_aln_len)); - - kalign_free_msa(msa); - - return OK; -ERROR: - if(msa){ - kalign_free_msa(msa); - } - return FAIL; -} - -int kalign_run_seeded(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - uint64_t tree_seed, float tree_noise, - float dist_scale, float vsm_amax, - float use_seq_weights, - int consistency_anchors, float consistency_weight) +/* ======================================================================== */ +/* kalign_single_run — unified internal entry point for one alignment run. */ +/* */ +/* Handles the full pipeline: input check, sort, alphabet conversion, tree */ +/* building, alignment, refinement, and optional realign iterations. */ +/* */ +/* The caller owns msa->pool (if USE_THREADPOOL). This function does NOT */ +/* create or destroy the threadpool — it reads msa->pool for parallel work. */ +/* ======================================================================== */ + +int kalign_single_run(struct msa *msa, + const struct kalign_run_config *cfg, + int n_threads) { struct aln_tasks* tasks = NULL; struct aln_param* ap = NULL; - /* This also adds the ranks of the sequences ! */ + int type; + int iter; + + /* Input check (also sets ranks) */ RUN(kalign_essential_input_check(msa, 0)); - /* If already aligned unalign ! */ if(msa->aligned != ALN_STATUS_UNALIGNED){ RUN(dealign_msa(msa)); } - /* Make sure sequences are in order */ RUN(msa_sort_len_name(msa)); - /* Convert into internal representation */ + /* Convert to reduced alphabet for tree building */ if(msa->biotype == ALN_BIOTYPE_DNA){ msa->L = ALPHA_defDNA; RUN(convert_msa_to_internal(msa, ALPHA_defDNA)); @@ -163,303 +163,113 @@ int kalign_run_seeded(struct msa *msa, int n_threads, int type, RUN(alloc_tasks(&tasks, msa->numseq)); -#ifdef HAVE_OPENMP +#if !defined(USE_THREADPOOL) && defined(HAVE_OPENMP) omp_set_num_threads(n_threads); #endif - /* Build guide tree - noisy variant if seed != 0 */ - if(tree_seed != 0 && tree_noise > 0.0f){ - RUN(build_tree_kmeans_noisy(msa, &tasks, tree_seed, tree_noise)); + + /* Build guide tree — noisy variant for ensemble diversity */ + if(cfg->tree_seed != 0 && cfg->tree_noise > 0.0f){ + RUN(build_tree_kmeans_noisy(msa, &tasks, cfg->tree_seed, cfg->tree_noise)); }else{ RUN(build_tree_kmeans(msa, &tasks)); } - /* Convert to full alphabet after having converted to reduced alphabet for tree building above */ + /* Convert to full alphabet for alignment */ if(msa->biotype == ALN_BIOTYPE_PROTEIN){ RUN(convert_msa_to_internal(msa, ALPHA_ambigiousPROTEIN)); } /* Resolve auto matrix selection using BPM distances */ - RUN(resolve_pfasum_auto(msa, &type)); - - /* align */ - RUN(aln_param_init(&ap, - msa->biotype, - n_threads, - type, - gpo, - gpe, - tgpe)); - ap->adaptive_budget = adaptive_budget; - if(use_seq_weights >= 0.0f){ - ap->use_seq_weights = use_seq_weights; - } - if(dist_scale > 0.0f){ - ap->dist_scale = dist_scale; - } - if(vsm_amax >= 0.0f){ - ap->vsm_amax = vsm_amax; - } - - if(ap->use_seq_weights > 0.0f){ - RUN(compute_tree_weights(msa, tasks)); - } - - /* Build anchor consistency table if requested */ - if(consistency_anchors > 0){ - ap->consistency_anchors = consistency_anchors; - ap->consistency_weight = consistency_weight; - RUN(anchor_consistency_build(msa, ap, consistency_anchors, - consistency_weight, - (struct consistency_table**)&msa->consistency_table)); - } - - DECLARE_TIMER(t1); - if(!msa->quiet){ - LOG_MSG("Aligning"); - } - START_TIMER(t1); - - if(refine == KALIGN_REFINE_INLINE){ - RUN(create_msa_tree_inline_refine(msa, ap, tasks, 3)); - }else{ - RUN(create_msa_tree(msa, ap, tasks)); - } - msa->aligned = ALN_STATUS_ALIGNED; - - /* Optional iterative refinement (two-pass approach) */ - if(refine != KALIGN_REFINE_NONE && refine != KALIGN_REFINE_INLINE){ - RUN(refine_alignment(msa, ap, tasks, refine)); - } - - /* Free consistency table AFTER refinement */ - if(msa->consistency_table){ - anchor_consistency_free((struct consistency_table*)msa->consistency_table); - msa->consistency_table = NULL; - } - - RUN(finalise_alignment(msa)); - - RUN(msa_sort_rank(msa)); - - STOP_TIMER(t1); - if(!msa->quiet){ - GET_TIMING(t1); - } - DESTROY_TIMER(t1); - - aln_param_free(ap); - free_tasks(tasks); - return OK; -ERROR: - if(msa->consistency_table){ - anchor_consistency_free((struct consistency_table*)msa->consistency_table); - msa->consistency_table = NULL; - } - aln_param_free(ap); - free_tasks(tasks); - return FAIL; -} - -int kalign_run(struct msa *msa, int n_threads, int type, float gpo, float gpe, float tgpe, int refine, int adaptive_budget) -{ - return kalign_run_seeded(msa, n_threads, type, gpo, gpe, tgpe, refine, adaptive_budget, 0, 0.0f, 0.0f, -1.0f, -1.0f, 0, 2.0f); -} - -int kalign_run_dist_scale(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - float use_seq_weights) -{ - struct aln_tasks* tasks = NULL; - struct aln_param* ap = NULL; - RUN(kalign_essential_input_check(msa, 0)); - - if(msa->aligned != ALN_STATUS_UNALIGNED){ - RUN(dealign_msa(msa)); - } - RUN(msa_sort_len_name(msa)); - - if(msa->biotype == ALN_BIOTYPE_DNA){ - msa->L = ALPHA_defDNA; - RUN(convert_msa_to_internal(msa, ALPHA_defDNA)); - }else if(msa->biotype == ALN_BIOTYPE_PROTEIN){ - msa->L = ALPHA_redPROTEIN; - RUN(convert_msa_to_internal(msa, ALPHA_redPROTEIN)); - }else{ - ERROR_MSG("Unable to determine what alphabet to use."); - } - - RUN(alloc_tasks(&tasks, msa->numseq)); - -#ifdef HAVE_OPENMP - omp_set_num_threads(n_threads); + type = cfg->matrix; + RUN(resolve_matrix_auto(msa, &type)); + + /* Init alignment parameters from config */ + RUN(aln_param_init(&ap, msa->biotype, n_threads, type, + cfg->gpo, cfg->gpe, cfg->tgpe)); +#ifdef USE_THREADPOOL + ap->pool = msa->pool; #endif - RUN(build_tree_kmeans(msa, &tasks)); - - if(msa->biotype == ALN_BIOTYPE_PROTEIN){ - RUN(convert_msa_to_internal(msa, ALPHA_ambigiousPROTEIN)); - } - RUN(resolve_pfasum_auto(msa, &type)); - - RUN(aln_param_init(&ap, - msa->biotype, - n_threads, - type, - gpo, - gpe, - tgpe)); - ap->adaptive_budget = adaptive_budget; - if(use_seq_weights >= 0.0f){ - ap->use_seq_weights = use_seq_weights; - } - ap->dist_scale = dist_scale; - if(vsm_amax >= 0.0f){ - ap->vsm_amax = vsm_amax; - } - - if(ap->use_seq_weights > 0.0f){ - RUN(compute_tree_weights(msa, tasks)); - } - - DECLARE_TIMER(t1); - if(!msa->quiet){ - LOG_MSG("Aligning (dist_scale=%.2f, vsm_amax=%.2f)", dist_scale, vsm_amax); - } - START_TIMER(t1); - - if(refine == KALIGN_REFINE_INLINE){ - RUN(create_msa_tree_inline_refine(msa, ap, tasks, 3)); - }else{ - RUN(create_msa_tree(msa, ap, tasks)); - } - msa->aligned = ALN_STATUS_ALIGNED; - - if(refine != KALIGN_REFINE_NONE && refine != KALIGN_REFINE_INLINE){ - RUN(refine_alignment(msa, ap, tasks, refine)); - } - - RUN(finalise_alignment(msa)); - RUN(msa_sort_rank(msa)); - - STOP_TIMER(t1); - if(!msa->quiet){ - GET_TIMING(t1); - } - DESTROY_TIMER(t1); - - aln_param_free(ap); - free_tasks(tasks); - return OK; -ERROR: - aln_param_free(ap); - free_tasks(tasks); - return FAIL; -} - -int kalign_run_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights, - int consistency_anchors, float consistency_weight) -{ - struct aln_tasks* tasks = NULL; - struct aln_param* ap = NULL; - int iter; - - RUN(kalign_essential_input_check(msa, 0)); - - if(msa->aligned != ALN_STATUS_UNALIGNED){ - RUN(dealign_msa(msa)); - } - RUN(msa_sort_len_name(msa)); - - if(msa->biotype == ALN_BIOTYPE_DNA){ - msa->L = ALPHA_defDNA; - RUN(convert_msa_to_internal(msa, ALPHA_defDNA)); - }else if(msa->biotype == ALN_BIOTYPE_PROTEIN){ - msa->L = ALPHA_redPROTEIN; - RUN(convert_msa_to_internal(msa, ALPHA_redPROTEIN)); - }else{ - ERROR_MSG("Unable to determine what alphabet to use."); - } - - RUN(alloc_tasks(&tasks, msa->numseq)); - -#ifdef HAVE_OPENMP - omp_set_num_threads(n_threads); -#endif - /* Initial guide tree from BPM anchor distances */ - RUN(build_tree_kmeans(msa, &tasks)); - - if(msa->biotype == ALN_BIOTYPE_PROTEIN){ - RUN(convert_msa_to_internal(msa, ALPHA_ambigiousPROTEIN)); - } - - RUN(resolve_pfasum_auto(msa, &type)); - - RUN(aln_param_init(&ap, - msa->biotype, - n_threads, - type, - gpo, - gpe, - tgpe)); - ap->adaptive_budget = adaptive_budget; - if(use_seq_weights >= 0.0f){ - ap->use_seq_weights = use_seq_weights; - } - ap->dist_scale = dist_scale; - if(vsm_amax >= 0.0f){ - ap->vsm_amax = vsm_amax; - } + /* Apply config overrides (config values are concrete, not sentinels) */ + ap->adaptive_budget = cfg->adaptive_budget; + ap->use_seq_weights = cfg->seq_weights; + ap->dist_scale = cfg->dist_scale; + ap->vsm_amax = cfg->vsm_amax; if(ap->use_seq_weights > 0.0f){ RUN(compute_tree_weights(msa, tasks)); } /* Build anchor consistency table if requested */ - if(consistency_anchors > 0){ - ap->consistency_anchors = consistency_anchors; - ap->consistency_weight = consistency_weight; - RUN(anchor_consistency_build(msa, ap, consistency_anchors, - consistency_weight, + if(cfg->consistency_anchors > 0){ + ap->consistency_anchors = cfg->consistency_anchors; + ap->consistency_weight = cfg->consistency_weight; + RUN(anchor_consistency_build(msa, ap, cfg->consistency_anchors, + cfg->consistency_weight, (struct consistency_table**)&msa->consistency_table)); } DECLARE_TIMER(t1); if(!msa->quiet){ - LOG_MSG("Aligning (realign=%d, dist_scale=%.2f, vsm_amax=%.2f)", - realign_iterations, dist_scale, vsm_amax); + LOG_MSG("Aligning"); } START_TIMER(t1); - /* First alignment with BPM-based guide tree */ - if(refine == KALIGN_REFINE_INLINE){ + /* Initial alignment */ + if(cfg->refine == KALIGN_REFINE_INLINE){ RUN(create_msa_tree_inline_refine(msa, ap, tasks, 3)); }else{ RUN(create_msa_tree(msa, ap, tasks)); } msa->aligned = ALN_STATUS_ALIGNED; - /* Iterative realignment: align -> compute distances -> new tree -> re-align */ - for(iter = 0; iter < realign_iterations; iter++){ + /* Iterative realignment: align → N×K anchor distances → bisecting + k-means tree → dealign → re-encode → re-align. + Sequences stay aligned until after tree build so the identity + distance callback can read gap characters from seq->seq. */ + for(iter = 0; iter < cfg->realign; iter++){ float** dm = NULL; + int* realign_anchors = NULL; + int n_realign_anchors = 0; int si; - /* Finalize to get character sequences with gap characters */ RUN(finalise_alignment(msa)); - /* Compute NxN pairwise identity distances from alignment */ - RUN(compute_aln_pairwise_dist(msa, &dm)); + /* Select K diverse anchors and compute N×K distances + (sequences still aligned — gap chars in seq->seq). */ + RUN(pick_anchor_from_alignment(msa, REALIGN_NUM_ANCHORS, + &realign_anchors, + &n_realign_anchors)); + RUN(compute_aln_anchor_dist(msa, realign_anchors, + n_realign_anchors, &dm)); + MFREE(realign_anchors); + + /* Rebuild guide tree via bisecting k-means. + The leaf-cluster callback uses identity distances from + the aligned sequences (seq->seq still has gap chars). */ + free_tasks(tasks); + tasks = NULL; + RUN(alloc_tasks(&tasks, msa->numseq)); + RUN(build_tree_kmeans_from_dm(msa, &tasks, dm, + n_realign_anchors, + aln_identity_pair_dist)); + + /* Free N×K matrix (AVX2-aware) */ + { + int fi; + for(fi = 0; fi < msa->numseq; fi++){ + if(dm[fi]){ +#ifdef HAVE_AVX2 + _mm_free(dm[fi]); +#else + MFREE(dm[fi]); +#endif + } + } + MFREE(dm); + } - /* Remove gaps, reset alignment status. - dealign_msa zeroes the gaps[] array but does NOT strip '-' - from seq->seq (which was linearized by finalise_alignment). - We must rebuild seq->seq without gap characters. */ + /* NOW dealign: strip gap characters from seq->seq. */ RUN(dealign_msa(msa)); for(si = 0; si < msa->numseq; si++){ struct msa_seq* seq = msa->sequences[si]; @@ -473,29 +283,21 @@ int kalign_run_realign(struct msa *msa, int n_threads, int type, seq->len = w; } - /* Re-encode internal representation for alignment */ + /* Re-encode for alignment */ if(msa->biotype == ALN_BIOTYPE_DNA){ RUN(convert_msa_to_internal(msa, ALPHA_defDNA)); }else if(msa->biotype == ALN_BIOTYPE_PROTEIN){ RUN(convert_msa_to_internal(msa, ALPHA_ambigiousPROTEIN)); } - /* Reset profile tracking */ RUN(set_sip_nsip(msa)); - /* Rebuild guide tree from alignment-derived distances */ - free_tasks(tasks); - tasks = NULL; - RUN(alloc_tasks(&tasks, msa->numseq)); - RUN(build_tree_from_pairwise(msa, &tasks, dm)); - free_aln_dm(dm, msa->numseq); - if(ap->use_seq_weights > 0.0f){ RUN(compute_tree_weights(msa, tasks)); } /* Re-align with new tree */ - if(refine == KALIGN_REFINE_INLINE){ + if(cfg->refine == KALIGN_REFINE_INLINE){ RUN(create_msa_tree_inline_refine(msa, ap, tasks, 3)); }else{ RUN(create_msa_tree(msa, ap, tasks)); @@ -503,9 +305,9 @@ int kalign_run_realign(struct msa *msa, int n_threads, int type, msa->aligned = ALN_STATUS_ALIGNED; } - /* Refinement after all realign iterations (two-pass, skip for inline) */ - if(refine != KALIGN_REFINE_NONE && refine != KALIGN_REFINE_INLINE){ - RUN(refine_alignment(msa, ap, tasks, refine)); + /* Two-pass refinement after the final alignment pass */ + if(cfg->refine != KALIGN_REFINE_NONE && cfg->refine != KALIGN_REFINE_INLINE){ + RUN(refine_alignment(msa, ap, tasks, cfg->refine)); } /* Free consistency table AFTER refinement */ @@ -527,148 +329,351 @@ int kalign_run_realign(struct msa *msa, int n_threads, int type, free_tasks(tasks); return OK; ERROR: - if(msa->consistency_table){ + if(msa && msa->consistency_table){ anchor_consistency_free((struct consistency_table*)msa->consistency_table); msa->consistency_table = NULL; } aln_param_free(ap); - free_tasks(tasks); + if(tasks) free_tasks(tasks); return FAIL; } -int kalign_post_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights) +/* Legacy entry point — thin wrapper around kalign_align_full */ +int kalign(char **seq, int *len, int numseq, int n_threads, int type, + float gpo, float gpe, float tgpe, char ***aligned, int *out_aln_len) { - struct aln_tasks* tasks = NULL; - struct aln_param* ap = NULL; - int iter; - - ASSERT(msa != NULL, "No MSA"); - ASSERT(realign_iterations > 0, "Need at least 1 realign iteration"); + struct msa *msa = NULL; + struct kalign_run_config cfg; - /* Detect biotype if not set */ - if(msa->biotype == ALN_BIOTYPE_UNDEF){ - RUN(detect_alphabet(msa)); + RUN(kalign_arr_to_msa(seq, len, numseq, &msa)); + msa->quiet = 1; + if(n_threads < 1){ + n_threads = 1; } - /* seq_distances available from prior alignment */ - RUN(resolve_pfasum_auto(msa, &type)); + cfg = kalign_run_config_defaults(); + cfg.matrix = type; + cfg.gpo = gpo; + cfg.gpe = gpe; + cfg.tgpe = tgpe; - RUN(aln_param_init(&ap, - msa->biotype, - n_threads, - type, - gpo, - gpe, - tgpe)); - ap->adaptive_budget = adaptive_budget; - if(use_seq_weights >= 0.0f){ - ap->use_seq_weights = use_seq_weights; - } - ap->dist_scale = dist_scale; - if(vsm_amax >= 0.0f){ - ap->vsm_amax = vsm_amax; - } + RUN(kalign_align_full(msa, &cfg, 1, NULL, n_threads)); -#ifdef HAVE_OPENMP - omp_set_num_threads(n_threads); -#endif + RUN(kalign_msa_to_arr(msa, aligned, out_aln_len)); + kalign_free_msa(msa); + return OK; +ERROR: + if(msa) kalign_free_msa(msa); + return FAIL; +} - DECLARE_TIMER(t1); - if(!msa->quiet){ - LOG_MSG("Post-realign (%d iterations, vsm_amax=%.2f)", - realign_iterations, ap->vsm_amax); - } - START_TIMER(t1); +/* ======================================================================== */ +/* Config defaults and unified entry point */ +/* ======================================================================== */ - for(iter = 0; iter < realign_iterations; iter++){ - float** dm = NULL; - int si; +struct kalign_run_config kalign_run_config_defaults(void) +{ + struct kalign_run_config cfg; + cfg.matrix = KALIGN_MATRIX_PFASUM43; + cfg.gpo = 7.0f; /* PFASUM43 default */ + cfg.gpe = 1.25f; + cfg.tgpe = 1.0f; + cfg.vsm_amax = 2.0f; /* protein default */ + cfg.seq_weights = 0.0f; + cfg.dist_scale = 0.0f; + cfg.refine = KALIGN_REFINE_NONE; + cfg.adaptive_budget = 0; + cfg.realign = 0; + cfg.tree_seed = 0; + cfg.tree_noise = 0.0f; + cfg.consistency_anchors = 0; + cfg.consistency_weight = 2.0f; + return cfg; +} - /* Finalize if not already (first iter may already be FINAL from ensemble) */ - if(msa->aligned != ALN_STATUS_FINAL){ - RUN(finalise_alignment(msa)); - } +struct kalign_ensemble_config kalign_ensemble_config_defaults(void) +{ + struct kalign_ensemble_config ens; + ens.min_support = 0; + ens.consistency_merge = 0; + ens.consistency_merge_weight = 2.0f; + return ens; +} - /* Compute NxN pairwise identity distances from alignment */ - RUN(compute_aln_pairwise_dist(msa, &dm)); +int kalign_align_full(struct msa* msa, + const struct kalign_run_config* runs, + int n_runs, + const struct kalign_ensemble_config* ens, + int n_threads) +{ +#ifdef USE_THREADPOOL + threadpool_t *pool = NULL; +#endif - /* Strip gap characters from seq->seq and fix seq->len. - Consensus alignment may have set len to alignment length, - so we recompute from the ungapped sequence. - We also zero gaps[] and reset alignment status manually - (rather than calling dealign_msa which uses the possibly - wrong len to bound the gaps[] loop). */ - for(si = 0; si < msa->numseq; si++){ - struct msa_seq* seq = msa->sequences[si]; - int r, w = 0; - for(r = 0; seq->seq[r] != '\0'; r++){ - if(seq->seq[r] != '-'){ - seq->seq[w++] = seq->seq[r]; - } - } - seq->seq[w] = '\0'; - seq->len = w; - /* Zero gaps array (len+1 entries) */ - for(r = 0; r <= w; r++){ - seq->gaps[r] = 0; - } - } - msa->aligned = ALN_STATUS_UNALIGNED; + ASSERT(msa != NULL, "No MSA"); + ASSERT(runs != NULL, "No run configs"); + ASSERT(n_runs >= 1, "n_runs must be >= 1"); - /* Re-encode to internal representation */ - if(msa->biotype == ALN_BIOTYPE_DNA){ - RUN(convert_msa_to_internal(msa, ALPHA_defDNA)); - }else if(msa->biotype == ALN_BIOTYPE_PROTEIN){ - RUN(convert_msa_to_internal(msa, ALPHA_ambigiousPROTEIN)); - } + if(n_threads < 1) n_threads = 1; - /* Reset profile tracking */ - RUN(set_sip_nsip(msa)); +#ifdef USE_THREADPOOL + pool = tp_create(n_threads); + msa->pool = pool; +#elif defined(HAVE_OPENMP) + omp_set_num_threads(n_threads); +#endif - /* Build UPGMA tree from alignment-derived distances */ - if(tasks){ free_tasks(tasks); tasks = NULL; } - RUN(alloc_tasks(&tasks, msa->numseq)); - RUN(build_tree_from_pairwise(msa, &tasks, dm)); - free_aln_dm(dm, msa->numseq); + if(n_runs > 1){ + RUN(kalign_ensemble_from_configs(msa, runs, n_runs, ens, n_threads)); + }else{ + RUN(kalign_single_run(msa, &runs[0], n_threads)); + } - if(ap->use_seq_weights > 0.0f){ - RUN(compute_tree_weights(msa, tasks)); - } +#ifdef USE_THREADPOOL + msa->pool = NULL; + tp_destroy(pool); +#endif + return OK; +ERROR: +#ifdef USE_THREADPOOL + msa->pool = NULL; + tp_destroy(pool); +#endif + return FAIL; +} - /* Re-align with new tree */ - if(refine == KALIGN_REFINE_INLINE){ - RUN(create_msa_tree_inline_refine(msa, ap, tasks, 3)); - }else{ - RUN(create_msa_tree(msa, ap, tasks)); - } - msa->aligned = ALN_STATUS_ALIGNED; - } +/* ======================================================================== */ +/* NSGA-III optimized protein mode presets */ +/* ======================================================================== */ - /* Refinement after all realign iterations (two-pass, skip for inline) */ - if(refine != KALIGN_REFINE_NONE && refine != KALIGN_REFINE_INLINE){ - RUN(refine_alignment(msa, ap, tasks, refine)); +/* Helper: fill one run config with preset values */ +static void preset_run(struct kalign_run_config *r, + int matrix, float gpo, float gpe, float tgpe, + float vsm_amax, float seq_weights, + int realign, int refine, + uint64_t seed, float noise) +{ + *r = kalign_run_config_defaults(); + r->matrix = matrix; + r->gpo = gpo; + r->gpe = gpe; + r->tgpe = tgpe; + r->vsm_amax = vsm_amax; + r->seq_weights = seq_weights; + r->realign = realign; + r->refine = refine; + r->tree_seed = seed; + r->tree_noise = noise; +} + +/* Helper: set shared consistency params on all runs */ +static void preset_consistency(struct kalign_run_config *runs, int n_runs, + int anchors, float weight) +{ + for(int i = 0; i < n_runs; i++){ + runs[i].consistency_anchors = anchors; + runs[i].consistency_weight = weight; } +} - RUN(finalise_alignment(msa)); - RUN(msa_sort_rank(msa)); +/* ---- Protein presets (NSGA-III optimized on BAliBASE v4, gen 41) ---- */ - STOP_TIMER(t1); - if(!msa->quiet){ - GET_TIMING(t1); - } - DESTROY_TIMER(t1); +static int preset_protein(const char *m, + struct kalign_run_config *runs, + int *n_runs, + struct kalign_ensemble_config *ens) +{ + /* fast: single run, ~34s on BAliBASE. F1=0.737 R=0.818 P=0.670 */ + if(strcasecmp(m, "fast") == 0){ + *n_runs = 1; + preset_run(&runs[0], + KALIGN_MATRIX_PFASUM60, + 8.626f, 0.843f, 0.433f, + 0.592f, 1.534f, + 0, KALIGN_REFINE_NONE, + 0, 0.0f); + return 0; + } + + /* default: single run with inline refinement, ~130s. + F1=0.750 R=0.779 P=0.724 */ + if(strcasecmp(m, "default") == 0){ + *n_runs = 1; + preset_run(&runs[0], + KALIGN_MATRIX_PFASUM60, + 8.121f, 0.684f, 0.560f, + 1.661f, 2.356f, + 0, KALIGN_REFINE_INLINE, + 0, 0.0f); + preset_consistency(runs, 1, 1, 1.640f); + return 0; + } + + /* recall: 5-run ensemble optimized for recall, ~1175s. + F1=0.777 R=0.837 P=0.726 */ + if(strcasecmp(m, "recall") == 0){ + *n_runs = 5; + preset_run(&runs[0], KALIGN_MATRIX_CORBLOSUM66, + 5.416f, 1.071f, 1.620f, + 2.536f, 0.282f, + 1, KALIGN_REFINE_ALL, 42, 0.0f); + preset_run(&runs[1], KALIGN_MATRIX_CORBLOSUM66, + 12.091f, 1.024f, 1.284f, + 2.078f, 0.282f, + 1, KALIGN_REFINE_INLINE, 43, 0.0f); + preset_run(&runs[2], KALIGN_MATRIX_PFASUM60, + 6.970f, 2.919f, 0.632f, + 0.877f, 0.282f, + 1, KALIGN_REFINE_NONE, 44, 0.0f); + preset_run(&runs[3], KALIGN_MATRIX_PFASUM43, + 6.173f, 1.102f, 0.510f, + 1.276f, 0.282f, + 1, KALIGN_REFINE_ALL, 45, 0.0f); + preset_run(&runs[4], KALIGN_MATRIX_PFASUM43, + 5.278f, 1.764f, 1.088f, + 2.315f, 0.282f, + 1, KALIGN_REFINE_CONFIDENT, 46, 0.0f); + preset_consistency(runs, 5, 3, 2.120f); + ens->min_support = 0; + return 0; + } + + /* accurate: 5-run ensemble optimized for F1, ~2005s. + F1=0.814 R=0.782 P=0.848 */ + if(strcasecmp(m, "accurate") == 0){ + *n_runs = 5; + preset_run(&runs[0], KALIGN_MATRIX_PFASUM43, + 8.682f, 0.650f, 1.465f, + 2.532f, 1.468f, + 1, KALIGN_REFINE_INLINE, 42, 0.0f); + preset_run(&runs[1], KALIGN_MATRIX_PFASUM43, + 6.148f, 1.174f, 1.297f, + 2.011f, 1.468f, + 1, KALIGN_REFINE_ALL, 43, 0.0f); + preset_run(&runs[2], KALIGN_MATRIX_CORBLOSUM66, + 3.622f, 1.004f, 0.631f, + 0.872f, 1.468f, + 1, KALIGN_REFINE_CONFIDENT, 44, 0.0f); + preset_run(&runs[3], KALIGN_MATRIX_CORBLOSUM66, + 5.988f, 0.552f, 0.495f, + 1.914f, 1.468f, + 1, KALIGN_REFINE_CONFIDENT, 45, 0.0f); + preset_run(&runs[4], KALIGN_MATRIX_PFASUM43, + 13.939f, 2.629f, 1.406f, + 1.830f, 1.468f, + 1, KALIGN_REFINE_NONE, 46, 0.0f); + preset_consistency(runs, 5, 8, 2.457f); + ens->min_support = 3; + return 0; + } + + return -1; +} - aln_param_free(ap); - free_tasks(tasks); - return OK; -ERROR: - aln_param_free(ap); - if(tasks) free_tasks(tasks); - return FAIL; +/* ---- Nucleotide presets (NSGA-III optimized on BRAliBASE + MDSA, gen 100) ---- + * + * Unified presets for both DNA and RNA. Optimized on combined + * BRAliBASE (599 RNA cases) + MDSA (325 DNA cases). + * All presets use Kimura two-parameter matrices. + */ + +static int preset_nucleotide(const char *m, + struct kalign_run_config *runs, + int *n_runs, + struct kalign_ensemble_config *ens) +{ + /* fast: single run with realign, ~5s. F1=0.790 R=0.792 P=0.788 */ + if(strcasecmp(m, "fast") == 0){ + *n_runs = 1; + preset_run(&runs[0], + KALIGN_MATRIX_NUC_200PAM, + 19.873f, 0.642f, 2.582f, + 0.874f, 0.199f, + 1, KALIGN_REFINE_NONE, + 0, 0.0f); + return 0; + } + + /* default: 3-run ensemble with realign, ~26s. + F1=0.806 R=0.773 P=0.842 */ + if(strcasecmp(m, "default") == 0){ + *n_runs = 3; + preset_run(&runs[0], KALIGN_MATRIX_NUC_20PAM, + 19.275f, 0.628f, 2.854f, + 0.445f, 0.448f, + 1, KALIGN_REFINE_NONE, 42, 0.0f); + preset_run(&runs[1], KALIGN_MATRIX_NUC_200PAM, + 16.581f, 0.274f, 2.491f, + 0.706f, 0.448f, + 1, KALIGN_REFINE_NONE, 43, 0.0f); + preset_run(&runs[2], KALIGN_MATRIX_NUC_200PAM, + 16.499f, 3.754f, 2.896f, + 0.491f, 0.448f, + 1, KALIGN_REFINE_CONFIDENT, 44, 0.0f); + ens->min_support = 2; + return 0; + } + + /* recall: single run with realign, ~17s. + F1=0.798 R=0.800 P=0.796 */ + if(strcasecmp(m, "recall") == 0){ + *n_runs = 1; + preset_run(&runs[0], + KALIGN_MATRIX_NUC_200PAM, + 19.882f, 0.646f, 2.854f, + 0.874f, 0.449f, + 1, KALIGN_REFINE_CONFIDENT, + 0, 0.0f); + preset_consistency(runs, 1, 1, 1.616f); + return 0; + } + + /* accurate: 5-run ensemble with realign, ~100s. + F1=0.810 R=0.760 P=0.867 */ + if(strcasecmp(m, "accurate") == 0){ + *n_runs = 5; + preset_run(&runs[0], KALIGN_MATRIX_NUC_200PAM, + 19.475f, 0.728f, 2.856f, + 0.492f, 0.590f, + 1, KALIGN_REFINE_CONFIDENT, 42, 0.0f); + preset_run(&runs[1], KALIGN_MATRIX_NUC_1PAM, + 19.377f, 1.379f, 2.486f, + 1.211f, 0.590f, + 1, KALIGN_REFINE_CONFIDENT, 43, 0.0f); + preset_run(&runs[2], KALIGN_MATRIX_NUC_200PAM, + 15.869f, 0.353f, 2.430f, + 0.721f, 0.590f, + 1, KALIGN_REFINE_NONE, 44, 0.0f); + preset_run(&runs[3], KALIGN_MATRIX_NUC_200PAM, + 15.989f, 2.331f, 2.764f, + 0.188f, 0.590f, + 1, KALIGN_REFINE_CONFIDENT, 45, 0.0f); + preset_run(&runs[4], KALIGN_MATRIX_NUC_1PAM, + 5.346f, 3.483f, 0.610f, + 1.406f, 0.590f, + 1, KALIGN_REFINE_NONE, 46, 0.0f); + preset_consistency(runs, 5, 2, 0.800f); + ens->min_support = 3; + return 0; + } + + return -1; } +int kalign_get_mode_preset(const char *mode, + int biotype, + struct kalign_run_config *runs, + int *n_runs, + struct kalign_ensemble_config *ens) +{ + const char *m = mode ? mode : "default"; + + *ens = kalign_ensemble_config_defaults(); + + if(biotype == ALN_BIOTYPE_DNA){ + return preset_nucleotide(m, runs, n_runs, ens); + } + + /* Default: protein presets */ + return preset_protein(m, runs, n_runs, ens); +} diff --git a/lib/src/aln_wrap.h b/lib/src/aln_wrap.h index dade933..fbaa66a 100644 --- a/lib/src/aln_wrap.h +++ b/lib/src/aln_wrap.h @@ -2,6 +2,7 @@ #define ALN_WRAP_H #include +#include #ifdef ALN_WRAP_IMPORT #define EXTERN @@ -17,28 +18,21 @@ struct msa; -EXTERN int kalign_run(struct msa *msa, int n_threads, int type, float gpo, float gpe, float tgpe, int refine, int adaptive_budget); -EXTERN int kalign_run_seeded(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - uint64_t tree_seed, float tree_noise, - float dist_scale, float vsm_amax, - float use_seq_weights, - int consistency_anchors, float consistency_weight); -EXTERN int kalign_run_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights, - int consistency_anchors, float consistency_weight); - -EXTERN int kalign_post_realign(struct msa *msa, int n_threads, int type, - float gpo, float gpe, float tgpe, - int refine, int adaptive_budget, - float dist_scale, float vsm_amax, - int realign_iterations, - float use_seq_weights); +/* Internal single-run entry point. Caller must set msa->pool before + calling (if USE_THREADPOOL is enabled). Does NOT create or destroy + the threadpool. Used by kalign_align_full and ensemble.c. */ +EXTERN int kalign_single_run(struct msa *msa, + const struct kalign_run_config *cfg, + int n_threads); + +EXTERN struct kalign_run_config kalign_run_config_defaults(void); +EXTERN struct kalign_ensemble_config kalign_ensemble_config_defaults(void); + +EXTERN int kalign_align_full(struct msa* msa, + const struct kalign_run_config* runs, + int n_runs, + const struct kalign_ensemble_config* ens, + int n_threads); #undef ALN_WRAP_IMPORT #undef EXTERN diff --git a/lib/src/anchor_consistency.c b/lib/src/anchor_consistency.c index c567d9d..0a769ab 100644 --- a/lib/src/anchor_consistency.c +++ b/lib/src/anchor_consistency.c @@ -10,6 +10,10 @@ #include "aln_setup.h" #include "aln_controller.h" +#ifdef USE_THREADPOOL +#include "threadpool/threadpool.h" +#endif + #define ANCHOR_CONSISTENCY_IMPORT #include "anchor_consistency.h" @@ -197,6 +201,87 @@ static int select_anchors(struct msa* msa, int K, int* anchor_ids) return FAIL; } +/* Serial build: each sequence i aligns against all K anchors */ +static int anchor_build_serial(struct msa* msa, struct aln_param* ap, + struct consistency_table* ct) +{ + int N = ct->numseq; + int K = ct->n_anchors; + int i, k; + + for(i = 0; i < N; i++){ + int len_i = msa->sequences[i]->len; + for(k = 0; k < K; k++){ + int ak = ct->anchor_ids[k]; + ct->map_lengths[i * K + k] = len_i; + + if(i == ak){ + int p; + MMALLOC(ct->pos_maps[i * K + k], sizeof(int) * len_i); + for(p = 0; p < len_i; p++){ + ct->pos_maps[i * K + k][p] = p; + } + }else{ + RUN(pairwise_align_map(ap, + msa->sequences[i]->s, len_i, + msa->sequences[ak]->s, msa->sequences[ak]->len, + &ct->pos_maps[i * K + k])); + } + } + } + return OK; +ERROR: + return FAIL; +} + +#ifdef USE_THREADPOOL +/* Context for parallel anchor build */ +struct anchor_par_ctx { + struct msa* msa; + struct aln_param* ap; + struct consistency_table* ct; + int error; /* set non-zero on failure */ +}; + +static void anchor_build_chunk(int start, int end, void* arg) +{ + struct anchor_par_ctx* ctx = (struct anchor_par_ctx*)arg; + struct msa* msa = ctx->msa; + struct aln_param* ap = ctx->ap; + struct consistency_table* ct = ctx->ct; + int K = ct->n_anchors; + int i, k; + + for(i = start; i < end; i++){ + int len_i = msa->sequences[i]->len; + for(k = 0; k < K; k++){ + int ak = ct->anchor_ids[k]; + ct->map_lengths[i * K + k] = len_i; + + if(i == ak){ + /* Identity map — no allocation can fail in practice + but we must handle it gracefully */ + int* map = malloc(sizeof(int) * len_i); + if(!map){ ctx->error = 1; return; } + for(int p = 0; p < len_i; p++){ + map[p] = p; + } + ct->pos_maps[i * K + k] = map; + }else{ + /* Pairwise alignment: each thread does its own DP */ + if(pairwise_align_map(ap, + msa->sequences[i]->s, len_i, + msa->sequences[ak]->s, msa->sequences[ak]->len, + &ct->pos_maps[i * K + k]) != OK){ + ctx->error = 1; + return; + } + } + } + } +} +#endif /* USE_THREADPOOL */ + int anchor_consistency_build(struct msa* msa, struct aln_param* ap, int n_anchors, float weight, struct consistency_table** ct_out) @@ -204,7 +289,7 @@ int anchor_consistency_build(struct msa* msa, struct aln_param* ap, struct consistency_table* ct = NULL; int N = msa->numseq; int K = n_anchors; - int i, k; + int i; if(K <= 0 || N < 3){ *ct_out = NULL; @@ -242,29 +327,25 @@ int anchor_consistency_build(struct msa* msa, struct aln_param* ap, LOG_MSG("Anchor consistency: K=%d, weight=%.1f", K, weight); } - /* Build position maps: for each sequence i, for each anchor k */ - for(i = 0; i < N; i++){ - int len_i = msa->sequences[i]->len; - for(k = 0; k < K; k++){ - int ak = ct->anchor_ids[k]; - ct->map_lengths[i * K + k] = len_i; - - if(i == ak){ - /* Identity map for anchor itself */ - int p; - MMALLOC(ct->pos_maps[i * K + k], sizeof(int) * len_i); - for(p = 0; p < len_i; p++){ - ct->pos_maps[i * K + k][p] = p; - } - }else{ - /* Pairwise alignment: seq_i vs anchor_k */ - RUN(pairwise_align_map(ap, - msa->sequences[i]->s, len_i, - msa->sequences[ak]->s, msa->sequences[ak]->len, - &ct->pos_maps[i * K + k])); - } + /* Build position maps: for each sequence i, for each anchor k. + This is the main bottleneck — O(N*K) pairwise DP alignments. */ +#ifdef USE_THREADPOOL + if(msa->pool != NULL){ + struct anchor_par_ctx ctx; + ctx.msa = msa; + ctx.ap = ap; + ctx.ct = ct; + ctx.error = 0; + tp_parallel_for(msa->pool, 0, N, anchor_build_chunk, &ctx); + if(ctx.error){ + ERROR_MSG("Parallel anchor build failed"); } + }else{ + RUN(anchor_build_serial(msa, ap, ct)); } +#else + RUN(anchor_build_serial(msa, ap, ct)); +#endif *ct_out = ct; return OK; @@ -274,19 +355,38 @@ int anchor_consistency_build(struct msa* msa, struct aln_param* ap, return FAIL; } +void sparse_bonus_free(struct sparse_bonus* sb) +{ + if(sb){ + if(sb->cols) MFREE(sb->cols); + if(sb->vals) MFREE(sb->vals); + MFREE(sb); + } +} + int anchor_consistency_get_bonus(struct consistency_table* ct, int seq_a, int len_a, int seq_b, int len_b, - float** bonus_out) + struct sparse_bonus** bonus_out) { - float* bonus = NULL; + struct sparse_bonus* sb = NULL; int* inv_b = NULL; int K = ct->n_anchors; int k, i; float per_anchor_weight = ct->weight / (float)K; - MMALLOC(bonus, sizeof(float) * len_a * len_b); - memset(bonus, 0, sizeof(float) * len_a * len_b); + MMALLOC(sb, sizeof(struct sparse_bonus)); + sb->cols = NULL; + sb->vals = NULL; + sb->n_rows = len_a; + sb->K = K; + + MMALLOC(sb->cols, sizeof(int) * len_a * K); + MMALLOC(sb->vals, sizeof(float) * len_a * K); + for(i = 0; i < len_a * K; i++){ + sb->cols[i] = -1; + sb->vals[i] = 0.0f; + } /* For each anchor, build inverse map for seq_b (anchor_pos → seq_b_pos), then scan seq_a's map to accumulate bonus. O(K * (La + Lb + max_anchor_len)) */ @@ -298,8 +398,6 @@ int anchor_consistency_get_bonus(struct consistency_table* ct, if(map_a == NULL || map_b == NULL) continue; - /* Determine anchor length for inverse map */ - /* Find max mapped position to size the inverse map */ anchor_len = 0; for(i = 0; i < len_a; i++){ if(map_a[i] >= anchor_len) anchor_len = map_a[i] + 1; @@ -309,7 +407,6 @@ int anchor_consistency_get_bonus(struct consistency_table* ct, } if(anchor_len == 0) continue; - /* Build inverse map: anchor_pos → seq_b_pos */ MMALLOC(inv_b, sizeof(int) * anchor_len); for(j = 0; j < anchor_len; j++){ inv_b[j] = -1; @@ -320,13 +417,23 @@ int anchor_consistency_get_bonus(struct consistency_table* ct, } } - /* Accumulate bonus */ + /* Accumulate bonus into sparse slots */ for(i = 0; i < len_a; i++){ int ak_pos = map_a[i]; if(ak_pos >= 0 && ak_pos < anchor_len){ int bj = inv_b[ak_pos]; if(bj >= 0){ - bonus[i * len_b + bj] += per_anchor_weight; + int base = i * K; + int slot = -1; + int s; + for(s = 0; s < K; s++){ + if(sb->cols[base + s] == bj){ slot = s; break; } + if(sb->cols[base + s] < 0){ slot = s; break; } + } + if(slot >= 0){ + sb->vals[base + slot] += per_anchor_weight; + sb->cols[base + slot] = bj; + } } } } @@ -335,10 +442,10 @@ int anchor_consistency_get_bonus(struct consistency_table* ct, inv_b = NULL; } - *bonus_out = bonus; + *bonus_out = sb; return OK; ERROR: - if(bonus) MFREE(bonus); + sparse_bonus_free(sb); if(inv_b) MFREE(inv_b); *bonus_out = NULL; return FAIL; @@ -470,9 +577,9 @@ int anchor_consistency_get_bonus_profile(struct consistency_table* ct, struct msa* msa, int node_a, int len_a, int node_b, int len_b, - float** bonus_out) + struct sparse_bonus** bonus_out) { - float* bonus = NULL; + struct sparse_bonus* sb = NULL; int* apos_a = NULL; float* conf_a = NULL; int* apos_b = NULL; @@ -483,8 +590,18 @@ int anchor_consistency_get_bonus_profile(struct consistency_table* ct, int k, i, j; float per_anchor_weight = ct->weight / (float)K; - MMALLOC(bonus, sizeof(float) * len_a * len_b); - memset(bonus, 0, sizeof(float) * len_a * len_b); + MMALLOC(sb, sizeof(struct sparse_bonus)); + sb->cols = NULL; + sb->vals = NULL; + sb->n_rows = len_a; + sb->K = K; + + MMALLOC(sb->cols, sizeof(int) * len_a * K); + MMALLOC(sb->vals, sizeof(float) * len_a * K); + for(i = 0; i < len_a * K; i++){ + sb->cols[i] = -1; + sb->vals[i] = 0.0f; + } MMALLOC(apos_a, sizeof(int) * len_a); MMALLOC(conf_a, sizeof(float) * len_a); @@ -523,14 +640,24 @@ int anchor_consistency_get_bonus_profile(struct consistency_table* ct, } } - /* Accumulate bonus */ + /* Accumulate bonus into sparse slots */ for(i = 0; i < len_a; i++){ int ak_pos = apos_a[i]; if(ak_pos >= 0 && ak_pos < anchor_len){ int bj = inv_b[ak_pos]; if(bj >= 0){ - bonus[i * len_b + bj] += - per_anchor_weight * conf_a[i] * inv_conf_b[ak_pos]; + float val = per_anchor_weight * conf_a[i] * inv_conf_b[ak_pos]; + int base = i * K; + int slot = -1; + int s; + for(s = 0; s < K; s++){ + if(sb->cols[base + s] == bj){ slot = s; break; } + if(sb->cols[base + s] < 0){ slot = s; break; } + } + if(slot >= 0){ + sb->vals[base + slot] += val; + sb->cols[base + slot] = bj; + } } } } @@ -546,10 +673,10 @@ int anchor_consistency_get_bonus_profile(struct consistency_table* ct, MFREE(apos_b); MFREE(conf_b); - *bonus_out = bonus; + *bonus_out = sb; return OK; ERROR: - if(bonus) MFREE(bonus); + sparse_bonus_free(sb); if(apos_a) MFREE(apos_a); if(conf_a) MFREE(conf_a); if(apos_b) MFREE(apos_b); diff --git a/lib/src/anchor_consistency.h b/lib/src/anchor_consistency.h index 4f46246..5fc3884 100644 --- a/lib/src/anchor_consistency.h +++ b/lib/src/anchor_consistency.h @@ -23,6 +23,28 @@ struct consistency_table { float weight; /* scaling factor for bonus */ }; +struct sparse_bonus { + int* cols; /* cols[i * K + k] = column index, or -1 if unused */ + float* vals; /* vals[i * K + k] = bonus value */ + int n_rows; /* = len_a (number of DP rows) */ + int K; /* max entries per row (= n_anchors) */ +}; + +static inline float sparse_bonus_lookup(const struct sparse_bonus* sb, int i, int j) +{ + float bonus = 0.0f; + const int base = i * sb->K; + int k; + for(k = 0; k < sb->K; k++){ + if(sb->cols[base + k] < 0) break; + if(sb->cols[base + k] == j) + bonus += sb->vals[base + k]; + } + return bonus; +} + +EXTERN void sparse_bonus_free(struct sparse_bonus* sb); + EXTERN int anchor_consistency_build(struct msa* msa, struct aln_param* ap, int n_anchors, float weight, struct consistency_table** ct_out); @@ -30,13 +52,13 @@ EXTERN int anchor_consistency_build(struct msa* msa, struct aln_param* ap, EXTERN int anchor_consistency_get_bonus(struct consistency_table* ct, int seq_a, int len_a, int seq_b, int len_b, - float** bonus_out); + struct sparse_bonus** bonus_out); EXTERN int anchor_consistency_get_bonus_profile(struct consistency_table* ct, struct msa* msa, int node_a, int len_a, int node_b, int len_b, - float** bonus_out); + struct sparse_bonus** bonus_out); EXTERN void anchor_consistency_free(struct consistency_table* ct); diff --git a/lib/src/bisectingKmeans.c b/lib/src/bisectingKmeans.c index e200be8..6e60516 100644 --- a/lib/src/bisectingKmeans.c +++ b/lib/src/bisectingKmeans.c @@ -2,6 +2,9 @@ #ifdef HAVE_OPENMP #include #endif +#ifdef USE_THREADPOOL +#include "threadpool.h" +#endif #ifdef HAVE_AVX2 #include @@ -51,13 +54,45 @@ static void create_tasks(struct node*n, struct aln_tasks* t); /* static int bisecting_kmeans_serial(struct msa *msa, struct node **ret_n, float **dm, int *samples, int num_samples); */ static int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * dm, - int* samples, int num_samples); + int* samples, int num_samples, + pair_dist_fn leaf_dist); /* static int bisecting_kmeans_parallel(struct msa* msa, struct node** ret_n, float** dm,int* samples, int num_samples); */ -static int split(const float * const * dm, int *samples, int num_anchors, int num_samples, - int seed_pick, struct kmeans_result **ret); static int split2(const float * const * dm,const int* samples, const int num_anchors,const int num_samples,const int seed_pick,struct kmeans_result** ret); +#ifdef USE_THREADPOOL +/* Wrapper args for threadpool task submission */ +struct split2_task_arg { + const float *const *dm; + const int *samples; + int num_anchors; + int num_samples; + int seed_idx; + struct kmeans_result **res; +}; + +static void split2_task_fn(void *arg) +{ + struct split2_task_arg *a = (struct split2_task_arg *)arg; + split2(a->dm, a->samples, a->num_anchors, a->num_samples, a->seed_idx, a->res); +} + +struct bisect_task_arg { + struct msa *msa; + struct node **ret_n; + const float *const *dm; + int *samples; + int num_samples; + pair_dist_fn leaf_dist; +}; + +static void bisect_task_fn(void *arg) +{ + struct bisect_task_arg *a = (struct bisect_task_arg *)arg; + bisecting_kmeans(a->msa, a->ret_n, a->dm, a->samples, a->num_samples, a->leaf_dist); +} +#endif + static inline int cmp_floats(const float a, const float b); inline int cmp_floats(const float a, const float b) @@ -72,6 +107,11 @@ inline int cmp_floats(const float a, const float b) } } +/* Default leaf-cluster distance: BPM on raw internal sequences */ +static float** bpm_pair_dist(struct msa* msa, int* samples, int n) +{ + return d_estimation(msa, samples, n, 1); +} int build_tree_kmeans_noisy(struct msa* msa, struct aln_tasks** tasks, uint64_t seed, float noise_sigma) @@ -131,11 +171,13 @@ int build_tree_kmeans_noisy(struct msa* msa, struct aln_tasks** tasks, LOG_MSG("Building guide tree."); } +#if !defined(USE_THREADPOOL) #ifdef HAVE_OPENMP #pragma omp parallel #pragma omp single nowait #endif - bisecting_kmeans(msa, &root, (const float * const *)dm, samples, numseq); +#endif + bisecting_kmeans(msa, &root, (const float * const *)dm, samples, numseq, bpm_pair_dist); STOP_TIMER(timer); if(!msa->quiet){ @@ -224,11 +266,13 @@ int build_tree_kmeans(struct msa* msa, struct aln_tasks** tasks) /* if(n_threads == 1){ */ /* RUN(bisecting_kmeans_serial(msa,&root, dm, samples, numseq)); */ /* }else{ */ +#if !defined(USE_THREADPOOL) #ifdef HAVE_OPENMP #pragma omp parallel #pragma omp single nowait #endif - bisecting_kmeans(msa,&root, (const float * const *)dm, samples, numseq); +#endif + bisecting_kmeans(msa,&root, (const float * const *)dm, samples, numseq, bpm_pair_dist); /* } */ STOP_TIMER(timer); @@ -270,7 +314,8 @@ int build_tree_kmeans(struct msa* msa, struct aln_tasks** tasks) return FAIL; } -int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * dm,int* samples, int num_samples) +int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * dm, + int* samples, int num_samples, pair_dist_fn leaf_dist) { struct kmeans_result* res_tmp = NULL; struct kmeans_result* best = NULL; @@ -287,17 +332,35 @@ int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * int num_l,num_r; /* LOG_MSG("num_samples: %d", num_samples); */ - num_anchors = MACRO_MIN(32, msa->numseq); - if(num_samples < KALIGN_KMEANS_UPGMA_THRESHOLD){ - float** dm = NULL; - RUNP(dm = d_estimation(msa, samples, num_samples,1));// anchors, num_anchors,1)); - n = upgma(dm,samples, num_samples); + /* Base cases: 0 or 1 samples cannot be split further */ + if(num_samples <= 0){ + return OK; + } + if(num_samples == 1){ + n = alloc_node(); + n->id = samples[0]; *ret_n = n; - gfree(dm); MFREE(samples); return OK; - //return n; + } + + num_anchors = MACRO_MIN(32, msa->numseq); + + /* K-means needs at least 3 samples for a non-degenerate bisection. + Floor the threshold to 3 so UPGMA handles tiny inputs safely. */ + { + int threshold = KALIGN_KMEANS_UPGMA_THRESHOLD; + if(threshold < 3) threshold = 3; + if(num_samples < threshold){ + float** dm_local = NULL; + RUNP(dm_local = leaf_dist(msa, samples, num_samples)); + n = upgma(dm_local, samples, num_samples); + *ret_n = n; + gfree(dm_local); + MFREE(samples); + return OK; + } } /* else if(num_samples < 1000){ */ @@ -321,24 +384,43 @@ int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * for(i = 0;i < tries;i += 4){ change = 0; +#ifdef USE_THREADPOOL + { + struct split2_task_arg args[4]; + for (int t = 0; t < 4; t++) { + args[t].dm = dm; + args[t].samples = samples; + args[t].num_anchors = num_anchors; + args[t].num_samples = num_samples; + args[t].seed_idx = ((i + t) * step) % num_samples; + args[t].res = &res[t]; + } + tp_group_t *g = tp_group_create(msa->pool); + for (int t = 0; t < 4; t++) + tp_group_submit(g, split2_task_fn, &args[t]); + tp_group_wait(g); + tp_group_destroy(g); + } +#else #ifdef HAVE_OPENMP #pragma omp task shared(dm,samples,num_anchors, num_samples,i,step,res) #endif - split2(dm,samples,num_anchors, num_samples, (i)*step, &res[0]); + split2(dm,samples,num_anchors, num_samples, ((i)*step) % num_samples, &res[0]); #ifdef HAVE_OPENMP #pragma omp task shared(dm,samples,num_anchors, num_samples,i,step,res) #endif - split2(dm,samples,num_anchors, num_samples, (i+ 1)*step, &res[1]); + split2(dm,samples,num_anchors, num_samples, ((i+ 1)*step) % num_samples, &res[1]); #ifdef HAVE_OPENMP #pragma omp task shared(dm,samples,num_anchors, num_samples,i,step,res) #endif - split2(dm,samples,num_anchors, num_samples, (i+ 2)*step, &res[2]); + split2(dm,samples,num_anchors, num_samples, ((i+ 2)*step) % num_samples, &res[2]); #ifdef HAVE_OPENMP #pragma omp task shared(dm,samples,num_anchors, num_samples,i,step,res) #endif - split2(dm,samples,num_anchors, num_samples, (i+ 3)*step, &res[3]); + split2(dm,samples,num_anchors, num_samples, ((i+ 3)*step) % num_samples, &res[3]); #ifdef HAVE_OPENMP #pragma omp taskwait +#endif #endif for(j = 0; j < 4;j++){ @@ -382,21 +464,30 @@ int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * MFREE(samples); n = alloc_node(); -/* #ifdef HAVE_OPENMP */ -/* #pragma omp parallel //num_threads(2) */ -/* #pragma omp single nowait */ +#ifdef USE_THREADPOOL + { + struct bisect_task_arg left_arg = { msa, &n->left, dm, sl, num_l, leaf_dist }; + struct bisect_task_arg right_arg = { msa, &n->right, dm, sr, num_r, leaf_dist }; + tp_group_t *g = tp_group_create(msa->pool); + tp_group_submit(g, bisect_task_fn, &left_arg); + tp_group_submit(g, bisect_task_fn, &right_arg); + tp_group_wait(g); + tp_group_destroy(g); + } +#else #ifdef HAVE_OPENMP #pragma omp task shared(msa,n,dm) #endif - bisecting_kmeans(msa,&n->left, dm, sl, num_l); + bisecting_kmeans(msa,&n->left, dm, sl, num_l, leaf_dist); #ifdef HAVE_OPENMP #pragma omp task shared(msa,n,dm,num_anchors) #endif - bisecting_kmeans(msa,&n->right, dm, sr, num_r); + bisecting_kmeans(msa,&n->right, dm, sr, num_r, leaf_dist); #ifdef HAVE_OPENMP #pragma omp taskwait +#endif #endif *ret_n =n; @@ -422,7 +513,7 @@ int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * /* num_anchors = MACRO_MIN(32, msa->numseq); */ -/* if(num_samples < KALIGN_KMEANS_UPGMA_THRESHOLD){ */ +/* if(num_samples < msa->kmeans_upgma_threshold){ */ /* float** dm = NULL; */ /* RUNP(dm = d_estimation(msa, samples, num_samples,1));// anchors, num_anchors,1)); */ /* n = upgma(dm,samples, num_samples); */ @@ -492,277 +583,6 @@ int bisecting_kmeans(struct msa* msa, struct node** ret_n, const float * const * /* return FAIL; */ /* } */ -int split(const float * const * dm,int* samples, int num_anchors,int num_samples,int seed_pick,struct kmeans_result** ret) -{ - struct kmeans_result* res = NULL; - int* sl = NULL; - int* sr = NULL; - int num_l,num_r; - float* w = NULL; - float* wl = NULL; - float* wr = NULL; - float* cl = NULL; - float* cr = NULL; - float dl = 0.0F; - float dr = 0.0F; - float score; - int num_var; - int i; - int s; - int j; - int stop = 0; - - num_var = num_anchors / 8; - if( num_anchors%8){ - num_var++; - } - num_var = num_var << 3; - - - - -#ifdef HAVE_AVX2 - wr = _mm_malloc(sizeof(float) * num_var,32); - wl = _mm_malloc(sizeof(float) * num_var,32); - cr = _mm_malloc(sizeof(float) * num_var,32); - cl = _mm_malloc(sizeof(float) * num_var,32); - w = _mm_malloc(sizeof(float) * num_var,32); -#else - MMALLOC(wr,sizeof(float) * num_var); - MMALLOC(wl,sizeof(float) * num_var); - MMALLOC(cr,sizeof(float) * num_var); - MMALLOC(cl,sizeof(float) * num_var); - MMALLOC(w,sizeof(float) * num_var); -#endif - - if(*ret){ - res = *ret; - }else{ - RUNP(res = alloc_kmeans_result(num_samples)); - } - - res->score = FLT_MAX; - - sl = res->sl; - sr = res->sr; - - - for(i = 0; i < num_var;i++){ - w[i] = 0.0F; - wr[i] = 0.0F; - wl[i] = 0.0F; - cr[i] = 0.0F; - cl[i] = 0.0F; - } - for(i = 0; i < num_samples;i++){ - s = samples[i]; - for(j = 0; j < num_anchors;j++){ - w[j] += dm[s][j]; - } - } - - for(j = 0; j < num_anchors;j++){ - w[j] /= (float)num_samples; - } - //r = tl_random_int(rng , num_samples); - //r = sel[t_iter]; - - s = samples[seed_pick]; - /* LOG_MSG("Selected %d\n",s); */ - for(j = 0; j < num_anchors;j++){ - cl[j] = dm[s][j]; - } - - for(j = 0; j < num_anchors;j++){ - cr[j] = w[j] - (cl[j] - w[j]); - fprintf(stdout,"BEGIN: %e %e diff::: %f %f\n", cl[j],cr[j], cl[j]-cr[j],w[j]); - - } - -#ifdef HAVE_AVX2 - _mm_free(w); -#else - MFREE(w); -#endif - - /* check if cr == cl - we have identical sequences */ - s = 0; - for(j = 0; j < num_anchors;j++){ - int res = cmp_floats(cl[j],cr[j]); - /* if(fabsf(cl[j]-cr[j]) > 1.0E-6){ */ - /* s = 1; */ - /* break; */ - /* } */ - if(res != 0){ - s++; - } - } - - fprintf(stdout,"S: %d\n",s); - s = 0; - for(j = 0; j < num_anchors;j++){ - /* int res = cmp_floats(dr,dl); */ - if(fabsf(cl[j]-cr[j]) > 1.0E-6){ - s++; - //break; - } - /* if(res != 0){ */ - /* s++; */ - /* } */ - } - - fprintf(stdout,"S: %d\n",s); -#ifdef HAVE_AVX2 - edist_256(cl,cr, num_anchors, &dr); -#else - edist_serial(cl, cr, num_anchors, &dr); -#endif - - - - fprintf(stdout,"R/L Dist: %e %e %d\n",dr,1e-6, dr < 1e-6); - cmp_floats(cl[j],cr[j]); - if(!s){ - score = 0.0F; - num_l = 0; - num_r = 0; - /* The code below caused sequence sets of size 1 to be passed to clustering... */ - /* sl[num_l] = samples[0]; */ - /* num_l++; */ - - /* for(i =1 ; i nl = num_l; - res->nr = num_r; - res->score = score; - *ret = res; - return OK; -ERROR: - return FAIL; -} - int split2(const float * const * dm,const int* samples, const int num_anchors,const int num_samples,const int seed_pick,struct kmeans_result** ret) { struct kmeans_result* res = NULL; @@ -1147,6 +967,75 @@ void free_kmeans_results(struct kmeans_result* k) } } +/* Build guide tree from a pre-computed N×K distance matrix using bisecting + k-means. Used by the realign loop where distances are computed from aligned + sequences (identity distances, already 0..1) rather than BPM on raw sequences. + + The caller owns dm and is responsible for freeing it. This function does NOT + free dm — unlike build_tree_kmeans which computes and frees its own dm. */ +int build_tree_kmeans_from_dm(struct msa* msa, struct aln_tasks** tasks, + float** dm, int num_anchors, + pair_dist_fn leaf_dist) +{ + struct aln_tasks* t = NULL; + struct node* root = NULL; + int* samples = NULL; + int numseq; + int i; + + ASSERT(msa != NULL, "No alignment."); + ASSERT(dm != NULL, "No distance matrix."); + ASSERT(num_anchors > 0, "num_anchors must be > 0"); + + t = *tasks; + if(!t){ + RUN(alloc_tasks(&t, msa->numseq)); + } + numseq = msa->numseq; + + MMALLOC(samples, sizeof(int) * numseq); + for(i = 0; i < numseq; i++){ + samples[i] = i; + } + + /* Bisecting k-means — same parallel algorithm as initial tree. + Note: bisecting_kmeans() takes ownership of samples and frees it. */ +#if !defined(USE_THREADPOOL) +#ifdef HAVE_OPENMP +#pragma omp parallel +#pragma omp single nowait +#endif +#endif + bisecting_kmeans(msa, &root, (const float * const *)dm, samples, numseq, leaf_dist); + samples = NULL; /* owned and freed by bisecting_kmeans */ + ASSERT(root != NULL, "Bisecting k-means tree construction failed."); + + label_internal(root, numseq); + create_tasks(root, t); + + /* Compute per-sequence mean distance to anchors. + Identity distances are already 0..1, so NO length normalization + (unlike BPM distances in build_tree_kmeans which divides by seq_len). */ + if(msa->seq_distances == NULL){ + MMALLOC(msa->seq_distances, sizeof(float) * numseq); + } + for(i = 0; i < numseq; i++){ + float sum = 0.0f; + int j; + for(j = 0; j < num_anchors; j++){ + sum += dm[i][j]; + } + msa->seq_distances[i] = sum / (float)num_anchors; + } + + MFREE(root); + *tasks = t; + return OK; +ERROR: + if(samples) MFREE(samples); + return FAIL; +} + int build_tree_from_pairwise(struct msa* msa, struct aln_tasks** tasks, float** dm) { struct aln_tasks* t = NULL; diff --git a/lib/src/bisectingKmeans.h b/lib/src/bisectingKmeans.h index 3d3d861..07188ac 100644 --- a/lib/src/bisectingKmeans.h +++ b/lib/src/bisectingKmeans.h @@ -3,7 +3,6 @@ #include - #ifdef BISECTINGKMEANS_IMPORT #define EXTERN #else @@ -14,16 +13,21 @@ #endif #endif - - - struct aln_tasks; struct msa; -/* int build_tree_kmeans(struct msa* msa, struct aln_param* ap,struct aln_tasks** task_list); */ + +/* Callback for computing pairwise distances within a leaf cluster. + Given a subset of sequence indices (samples[0..n-1]), return an n×n + symmetric distance matrix. The caller (UPGMA) frees it via gfree(). + Returns NULL on failure. */ +typedef float** (*pair_dist_fn)(struct msa* msa, int* samples, int n); EXTERN int build_tree_kmeans(struct msa* msa, struct aln_tasks** tasks); EXTERN int build_tree_kmeans_noisy(struct msa* msa, struct aln_tasks** tasks, uint64_t seed, float noise_sigma); +EXTERN int build_tree_kmeans_from_dm(struct msa* msa, struct aln_tasks** tasks, + float** dm, int num_anchors, + pair_dist_fn leaf_dist); EXTERN int build_tree_from_pairwise(struct msa* msa, struct aln_tasks** tasks, float** dm); diff --git a/lib/src/bpm.c b/lib/src/bpm.c index 2ecaf34..9aa0d52 100644 --- a/lib/src/bpm.c +++ b/lib/src/bpm.c @@ -18,7 +18,6 @@ __m256i BROADCAST_MASK[16]; void bitShiftLeft256ymm (__m256i *data, int count); -__m256i bitShiftRight256ymm (__m256i *data, int count); /* taken from Alexander Yee: http://www.numberworld.org/y-cruncher/internals/addition.html#ks_add */ __m256i add256(uint32_t carry, __m256i A, __m256i B); @@ -335,20 +334,6 @@ void bitShiftLeft256ymm (__m256i *data, int count) //return carryOut; } -__m256i bitShiftRight256ymm (__m256i *data, int count) -{ - __m256i innerCarry, carryOut, rotate; - - - innerCarry = _mm256_slli_epi64(*data, 64 - count); - rotate = _mm256_permute4x64_epi64 (innerCarry, 0x39); - innerCarry = _mm256_blend_epi32 (_mm256_setzero_si256 (), rotate, 0x3F); - *data = _mm256_srli_epi64(*data, count); - *data = _mm256_or_si256(*data, innerCarry); - - carryOut = _mm256_xor_si256 (innerCarry, rotate); //FIXME: not sure if this is correct!!! - return carryOut; -} #endif diff --git a/lib/src/consensus_msa.c b/lib/src/consensus_msa.c index faad1f3..cf19431 100644 --- a/lib/src/consensus_msa.c +++ b/lib/src/consensus_msa.c @@ -6,6 +6,10 @@ #include "msa_alloc.h" #include "poar.h" +#ifdef USE_THREADPOOL +#include "threadpool/threadpool.h" +#endif + #define CONSENSUS_MSA_IMPORT #include "consensus_msa.h" @@ -369,10 +373,72 @@ static int topo_sort(int* col_id, /* [total_residues]: element -> column return FAIL; } +#ifdef USE_THREADPOOL +struct cand_count_ctx { + struct poar_table* table; + int numseq; + int min_support; + int* row_counts; +}; + +static void cand_count_chunk(int start, int end, void* arg) +{ + struct cand_count_ctx* c = (struct cand_count_ctx*)arg; + for(int i = start; i < end; i++){ + int count = 0; + for(int j = i + 1; j < c->numseq; j++){ + int pidx = pair_index(i, j, c->numseq); + struct poar_pair* pp = c->table->pairs[pidx]; + for(int e = 0; e < pp->n_entries; e++){ + if(popcount32(pp->entries[e].support) >= c->min_support){ + count++; + } + } + } + c->row_counts[i] = count; + } +} + +struct cand_fill_ctx { + struct poar_table* table; + int numseq; + int min_support; + int* seq_offsets; + int* row_offsets; + struct merge_candidate* candidates; +}; + +static void cand_fill_chunk(int start, int end, void* arg) +{ + struct cand_fill_ctx* c = (struct cand_fill_ctx*)arg; + for(int i = start; i < end; i++){ + int pos = c->row_offsets[i]; + for(int j = i + 1; j < c->numseq; j++){ + int pidx = pair_index(i, j, c->numseq); + struct poar_pair* pp = c->table->pairs[pidx]; + for(int e = 0; e < pp->n_entries; e++){ + int support = popcount32(pp->entries[e].support); + if(support >= c->min_support){ + uint32_t key = pp->entries[e].key; + c->candidates[pos].elem_i = c->seq_offsets[i] + (int)(key >> 20); + c->candidates[pos].elem_j = c->seq_offsets[j] + (int)(key & 0xFFFFF); + c->candidates[pos].support = support; + pos++; + } + } + } + } +} +#endif + int build_consensus(struct poar_table* table, int* seq_lengths, int numseq, int min_support, - struct msa* out_msa) + struct msa* out_msa +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { struct uf_set* uf = NULL; int* seq_offsets = NULL; @@ -409,28 +475,64 @@ int build_consensus(struct poar_table* table, in descending support order so higher-confidence pairs merge first */ { int n_candidates = 0; - int alloc_candidates = 1024; struct merge_candidate *candidates = NULL; - MMALLOC(candidates, sizeof(*candidates) * alloc_candidates); - - for(i = 0; i < numseq - 1; i++){ - for(j = i + 1; j < numseq; j++){ - int pidx = pair_index(i, j, numseq); - struct poar_pair* pp = table->pairs[pidx]; +#ifdef USE_THREADPOOL + if(pool != NULL && numseq > 16){ + /* Two-pass parallel: count then fill */ + int* row_counts = NULL; + int* row_offsets = NULL; + MMALLOC(row_counts, sizeof(int) * numseq); + MMALLOC(row_offsets, sizeof(int) * numseq); + + struct cand_count_ctx cc = { table, numseq, min_support, row_counts }; + tp_parallel_for(pool, 0, numseq - 1, cand_count_chunk, &cc); + row_counts[numseq - 1] = 0; + + /* Prefix sum for offsets */ + row_offsets[0] = 0; + for(i = 1; i < numseq; i++){ + row_offsets[i] = row_offsets[i-1] + row_counts[i-1]; + } + n_candidates = row_offsets[numseq-1] + row_counts[numseq-1]; + + if(n_candidates > 0){ + MMALLOC(candidates, sizeof(*candidates) * n_candidates); + struct cand_fill_ctx cf = { + table, numseq, min_support, + seq_offsets, row_offsets, candidates + }; + tp_parallel_for(pool, 0, numseq - 1, cand_fill_chunk, &cf); + }else{ + MMALLOC(candidates, sizeof(*candidates) * 1); + } - for(int e = 0; e < pp->n_entries; e++){ - int support = popcount32(pp->entries[e].support); - if(support >= min_support){ - if(n_candidates >= alloc_candidates){ - alloc_candidates *= 2; - MREALLOC(candidates, sizeof(*candidates) * alloc_candidates); + MFREE(row_counts); + MFREE(row_offsets); + }else +#endif + { + int alloc_candidates = 1024; + MMALLOC(candidates, sizeof(*candidates) * alloc_candidates); + + for(i = 0; i < numseq - 1; i++){ + for(j = i + 1; j < numseq; j++){ + int pidx = pair_index(i, j, numseq); + struct poar_pair* pp = table->pairs[pidx]; + + for(int e = 0; e < pp->n_entries; e++){ + int support = popcount32(pp->entries[e].support); + if(support >= min_support){ + if(n_candidates >= alloc_candidates){ + alloc_candidates *= 2; + MREALLOC(candidates, sizeof(*candidates) * alloc_candidates); + } + uint32_t key = pp->entries[e].key; + candidates[n_candidates].elem_i = seq_offsets[i] + (int)(key >> 20); + candidates[n_candidates].elem_j = seq_offsets[j] + (int)(key & 0xFFFFF); + candidates[n_candidates].support = support; + n_candidates++; } - uint32_t key = pp->entries[e].key; - candidates[n_candidates].elem_i = seq_offsets[i] + (int)(key >> 20); - candidates[n_candidates].elem_j = seq_offsets[j] + (int)(key & 0xFFFFF); - candidates[n_candidates].support = support; - n_candidates++; } } } @@ -561,8 +663,69 @@ int build_consensus(struct poar_table* table, - confidence = sum(supports) / (n_residue_pairs * n_alignments) Gaps get confidence 0.0. Column confidence = mean of residue confidences in that column. */ +#ifdef USE_THREADPOOL +struct confidence_ctx { + struct poar_table* table; + struct pos_matrix* pm; + struct msa* msa; + int numseq; + int alnlen; + int n_alignments; +}; + +static void confidence_chunk(int start, int end, void* arg) +{ + struct confidence_ctx* c = (struct confidence_ctx*)arg; + int j, col; + for(int i = start; i < end; i++){ + for(col = 0; col < c->alnlen; col++){ + int ri = c->pm->col_to_res[i][col]; + if(ri < 0){ + c->msa->sequences[i]->confidence[col] = 0.0f; + continue; + } + double sum_support = 0.0; + int n_pairs = 0; + for(j = 0; j < c->numseq; j++){ + if(j == i) continue; + int rj = c->pm->col_to_res[j][col]; + if(rj < 0) continue; + int si = i < j ? i : j; + int sj = i < j ? j : i; + int pidx = pair_index(si, sj, c->numseq); + struct poar_pair* pp = c->table->pairs[pidx]; + int orig_i = i < j ? ri : rj; + int orig_j = i < j ? rj : ri; + uint32_t key = ((uint32_t)orig_i << 20) | (uint32_t)orig_j; + int lo = 0, hi = pp->n_entries, support = 0; + while(lo < hi){ + int mid = lo + (hi - lo) / 2; + if(pp->entries[mid].key < key) lo = mid + 1; + else if(pp->entries[mid].key == key){ + support = popcount32(pp->entries[mid].support); + break; + }else hi = mid; + } + sum_support += (double)support; + n_pairs++; + } + if(n_pairs > 0 && c->n_alignments > 0){ + c->msa->sequences[i]->confidence[col] = + (float)(sum_support / ((double)n_pairs * (double)c->n_alignments)); + }else{ + c->msa->sequences[i]->confidence[col] = 0.0f; + } + } + } +} +#endif + int compute_residue_confidence(struct poar_table* table, - struct msa* aligned_msa) + struct msa* aligned_msa +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { struct pos_matrix* pm = NULL; int numseq = aligned_msa->numseq; @@ -603,6 +766,15 @@ int compute_residue_confidence(struct poar_table* table, MMALLOC(aligned_msa->col_confidence, sizeof(float) * alnlen); /* Compute per-residue confidence */ +#ifdef USE_THREADPOOL + if(pool != NULL && numseq > 16){ + struct confidence_ctx ctx = { + table, pm, aligned_msa, numseq, alnlen, n_alignments + }; + tp_parallel_for(pool, 0, numseq, confidence_chunk, &ctx); + }else +#endif + { for(i = 0; i < numseq; i++){ for(col = 0; col < alnlen; col++){ int ri = pm->col_to_res[i][col]; @@ -658,6 +830,7 @@ int compute_residue_confidence(struct poar_table* table, } } } + } /* end serial fallback */ /* Compute per-column confidence: mean over non-gap residues */ for(col = 0; col < alnlen; col++){ @@ -691,45 +864,108 @@ int compute_residue_confidence(struct poar_table* table, agreeing. Summing these gives expected correct pairs. This rewards both high recall (many pairs) and high precision (pairs with broad agreement). */ +#ifdef USE_THREADPOOL +struct score_poar_ctx { + struct poar_table* table; + struct pos_matrix* pm; + int numseq; + int alnlen; + double denom; + double* row_scores; +}; + +static void score_poar_chunk(int start, int end, void* arg) +{ + struct score_poar_ctx* c = (struct score_poar_ctx*)arg; + int j, col; + for(int i = start; i < end; i++){ + double row_sum = 0.0; + for(j = i + 1; j < c->numseq; j++){ + int pidx = pair_index(i, j, c->numseq); + struct poar_pair* pp = c->table->pairs[pidx]; + for(col = 0; col < c->alnlen; col++){ + int ri = c->pm->col_to_res[i][col]; + int rj = c->pm->col_to_res[j][col]; + if(ri >= 0 && rj >= 0){ + uint32_t key = ((uint32_t)ri << 20) | (uint32_t)rj; + int lo = 0, hi = pp->n_entries, support = 0; + while(lo < hi){ + int mid = lo + (hi - lo) / 2; + if(pp->entries[mid].key < key) lo = mid + 1; + else if(pp->entries[mid].key == key){ + support = popcount32(pp->entries[mid].support); + break; + }else hi = mid; + } + row_sum += (double)(support - 1) / c->denom; + } + } + } + c->row_scores[i] = row_sum; + } +} +#endif + int score_alignment_poar(struct poar_table* table, struct pos_matrix* pm, int numseq, int n_alignments, - double* out_score) + double* out_score +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { double total_score = 0.0; - int i, j, col; - int alnlen = pm->alnlen; + int i; double denom = (n_alignments > 1) ? (double)(n_alignments - 1) : 1.0; - for(i = 0; i < numseq - 1; i++){ - for(j = i + 1; j < numseq; j++){ - int pidx = pair_index(i, j, numseq); - struct poar_pair* pp = table->pairs[pidx]; +#ifdef USE_THREADPOOL + if(pool != NULL && numseq > 16){ + double* row_scores = NULL; + MMALLOC(row_scores, sizeof(double) * numseq); + for(i = 0; i < numseq; i++) row_scores[i] = 0.0; - for(col = 0; col < alnlen; col++){ - int ri = pm->col_to_res[i][col]; - int rj = pm->col_to_res[j][col]; - if(ri >= 0 && rj >= 0){ - uint32_t key = ((uint32_t)ri << 20) | (uint32_t)rj; + struct score_poar_ctx ctx = { + table, pm, numseq, pm->alnlen, denom, row_scores + }; + tp_parallel_for(pool, 0, numseq - 1, score_poar_chunk, &ctx); - /* Binary search in sorted entries */ - int lo = 0; - int hi = pp->n_entries; - int support = 0; - while(lo < hi){ - int mid = lo + (hi - lo) / 2; - if(pp->entries[mid].key < key){ - lo = mid + 1; - }else if(pp->entries[mid].key == key){ - support = popcount32(pp->entries[mid].support); - break; - }else{ - hi = mid; + for(i = 0; i < numseq - 1; i++){ + total_score += row_scores[i]; + } + MFREE(row_scores); + + *out_score = total_score; + return OK; + ERROR: + if(row_scores) MFREE(row_scores); + return FAIL; + } +#endif + { + int j, col; + int alnlen = pm->alnlen; + for(i = 0; i < numseq - 1; i++){ + for(j = i + 1; j < numseq; j++){ + int pidx = pair_index(i, j, numseq); + struct poar_pair* pp = table->pairs[pidx]; + for(col = 0; col < alnlen; col++){ + int ri = pm->col_to_res[i][col]; + int rj = pm->col_to_res[j][col]; + if(ri >= 0 && rj >= 0){ + uint32_t key = ((uint32_t)ri << 20) | (uint32_t)rj; + int lo = 0, hi = pp->n_entries, support = 0; + while(lo < hi){ + int mid = lo + (hi - lo) / 2; + if(pp->entries[mid].key < key) lo = mid + 1; + else if(pp->entries[mid].key == key){ + support = popcount32(pp->entries[mid].support); + break; + }else hi = mid; } + total_score += (double)(support - 1) / denom; } - /* support includes self; subtract 1 for other-agreement */ - total_score += (double)(support - 1) / denom; } } } diff --git a/lib/src/consensus_msa.h b/lib/src/consensus_msa.h index 23fc70b..dd36ee5 100644 --- a/lib/src/consensus_msa.h +++ b/lib/src/consensus_msa.h @@ -15,6 +15,27 @@ struct poar_table; struct pos_matrix; struct msa; +#ifdef USE_THREADPOOL +typedef struct threadpool threadpool_t; + +EXTERN int build_consensus(struct poar_table* table, + int* seq_lengths, int numseq, + int min_support, + struct msa* out_msa, + threadpool_t* pool); + +EXTERN int score_alignment_poar(struct poar_table* table, + struct pos_matrix* pm, + int numseq, + int n_alignments, + double* out_score, + threadpool_t* pool); + +EXTERN int compute_residue_confidence(struct poar_table* table, + struct msa* aligned_msa, + threadpool_t* pool); +#else + EXTERN int build_consensus(struct poar_table* table, int* seq_lengths, int numseq, int min_support, @@ -28,6 +49,7 @@ EXTERN int score_alignment_poar(struct poar_table* table, EXTERN int compute_residue_confidence(struct poar_table* table, struct msa* aligned_msa); +#endif #undef CONSENSUS_MSA_IMPORT #undef EXTERN diff --git a/lib/src/coretralign.c b/lib/src/coretralign.c deleted file mode 100644 index 5c89bc8..0000000 --- a/lib/src/coretralign.c +++ /dev/null @@ -1,244 +0,0 @@ -#include "tldevel.h" - -#define CORETRALIGN_IMPORT -#include "coretralign.h" - -int aln_scheduler_get_tid(aln_scheduler *s) -{ - int i; - int64_t tid = (int64_t) pthread_self(); - int ID = -1; - aln_scheduler_lock(s); - for(i = 0; i < s->thread_id_idx;i++){ - if(s->thread_id_map[i] == tid){ - ID = i; - break; - } - } - if(ID == -1){ - ID = s->thread_id_idx; - s->thread_id_map[s->thread_id_idx] = tid; - s->thread_id_idx++; - } - aln_scheduler_unlock(s); - return ID; -} - -int aln_scheduler_lock(aln_scheduler* s) -{ - ASSERT(s != NULL, "No thread controll"); - /* pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); */ - if(pthread_mutex_lock(&s->lock) != 0){ - ERROR_MSG("Can't get lock"); - } - return OK; -ERROR: - return FAIL; -} - -int aln_scheduler_trylock(aln_scheduler* s) -{ - return pthread_mutex_trylock(&s->lock); -} - -int aln_scheduler_unlock(aln_scheduler* s) -{ - ASSERT(s != NULL, "No thread controll"); - if(pthread_mutex_unlock(&s->lock) != 0){ - ERROR_MSG("Can't get lock"); - } - /* pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); */ - /* pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); */ - /* pthread_testcancel(); */ - return OK; -ERROR: - return FAIL; -} - - -int aln_scheduler_alloc(aln_scheduler **scheduler, int n_threads) -{ - aln_scheduler* n = NULL; - - MMALLOC(n, sizeof(aln_scheduler)); - n->task_alloc = NULL; - n->task_queue = NULL; - n->root = NULL; - n->threads = NULL; - n->thread_id_map = NULL; - n->thread_id_idx = 0; - pthread_mutex_init(&n->lock, NULL); - n->n_threads = n_threads; - - MMALLOC(n->threads, sizeof(pthread_t) * n->n_threads); - - MMALLOC(n->thread_id_map, sizeof(int64_t) * n->n_threads); - - - queue_alloc(&n->task_alloc, 1); - queue_alloc(&n->task_queue, 0); - - - - *scheduler = n; - return OK; -ERROR: - return FAIL; -} - -void aln_scheduler_free(aln_scheduler *n) -{ - if(n){ - if(n->task_alloc){ - queue_free(n->task_alloc); - } - if(n->task_queue){ - queue_free(n->task_queue); - } - if(n->threads){ - MFREE(n->threads); - } - if(n->thread_id_map){ - MFREE(n->thread_id_map); - } - MFREE(n); - } - -} - - - -static int aln_elem_alloc(aln_elem **node); -static int aln_elem_free(aln_elem *n); - -int queue_push(aln_elem_queue *q, aln_elem *n) -{ - if(q->head == NULL){ - q->head = n; - q->tail = q->head; - q->head->p = NULL; - }else{ - n->p = q->head; - q->head = n; - } - q->n++; - return OK; -} - -int queue_pop(aln_elem_queue *q, aln_elem **node) -{ - aln_elem * tmp = NULL; - if(q->head == NULL){ - if(q->is_allocator){ - /* RUN(eq_add_mem(q)); */ - }else{ - *node = NULL; - return FAIL; - } - } - - *node = q->head; - tmp = q->head; - q->head = tmp->p; - - if(q->head == NULL){ - q->tail = NULL; - } - q->n--; - - return OK; -} - -int queue_append(aln_elem_queue *q, aln_elem* n) -{ - - if(q->head == NULL){ - q->head = n; - q->tail = q->head; - - }else{ - q->tail->p = n; - q->tail = q->tail->p; - } - q->tail->p = NULL; - q->n++; - return OK; -} - - -int queue_add_mem(aln_elem_queue *q) -{ - ASSERT(q->alloc_block_size != 0,"This queue is not an allocator!"); - - for(int i = 0; i < q->alloc_block_size;i++){ - /* Alloc e_node */ - aln_elem* n = NULL; - aln_elem_alloc(&n); - /* Add to queue */ - queue_append(q, n); - } - return OK; -ERROR: - - return FAIL; -} - -int queue_alloc(aln_elem_queue **queue, uint8_t is_allocator) -{ - aln_elem_queue* l = NULL; - MMALLOC(l, sizeof(aln_elem_queue)); - l->head = NULL; - l->tail = 0; - l->n = 0; - l->alloc_block_size = 0; - - l->is_allocator = is_allocator; - if(is_allocator){ - l->alloc_block_size = 4096; - RUN(queue_add_mem(l)); - /* RUN(eq_add_mem(l)); */ - } - *queue = l; - return OK; -ERROR: - queue_free(l); - return FAIL; -} - -void queue_free(aln_elem_queue *l) -{ - if(l){ - aln_elem* n = NULL; - while(l->n){ - n = NULL; - queue_pop(l, &n); - aln_elem_free(n); - } - MFREE(l); - } -} - -int aln_elem_alloc(aln_elem **node) -{ - aln_elem* n = NULL; - - MMALLOC(n, sizeof(aln_elem)); - n->l = NULL; - n->r = NULL; - n->p = NULL; - n->wait = 0; - n->type = AE_TYPE_UNDEF; - n->aln_mem = NULL; - *node = n; - return OK; -ERROR: - aln_elem_free(n); - return FAIL; -} - -int aln_elem_free(aln_elem *n) -{ - if(n){ - MFREE(n); - } -} diff --git a/lib/src/coretralign.h b/lib/src/coretralign.h deleted file mode 100644 index a8671da..0000000 --- a/lib/src/coretralign.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef CORETRALIGN_H -#define CORETRALIGN_H - -#include -#include -#include "aln_mem.h" - -#ifdef CORETRALIGN_IMPORT -#define EXTERN -#else -#define EXTERN extern -#endif - -/* Idea: - construct hirschberg alignment via trees - -> only because this would make threading easier to control -*/ - -typedef enum { - AE_TYPE_UNDEF, - AE_TYPE_ALN_BACK, - AE_TYPE_ALN_FORWARD, - AE_TYPE_SPLIT - /* tree stuff here */ -}aln_elem_type; - -typedef struct aln_elem aln_elem; -typedef struct aln_elem { - aln_elem* l; /* left OR backward in dyn*/ - aln_elem* r; /* right OR backward in dyn*/ - aln_elem* p; /* parent */ - uint8_t wait; - aln_elem_type type; - struct aln_mem* aln_mem; -} aln_elem; - -typedef struct aln_elem_queue { - aln_elem* head; - aln_elem* tail; - int alloc_block_size; - int n; - uint8_t is_allocator; -} aln_elem_queue; - -typedef struct aln_scheduler { - aln_elem_queue* task_alloc; - aln_elem_queue* task_queue; - aln_elem* root; - pthread_t* threads; - int64_t* thread_id_map; - int thread_id_idx; - pthread_mutex_t lock; - int n_threads; - double** dm; - struct msa* msa; -} aln_scheduler; - -EXTERN int aln_scheduler_lock(aln_scheduler* s); -EXTERN int aln_scheduler_trylock(aln_scheduler* s); -EXTERN int aln_scheduler_unlock(aln_scheduler* s); - -EXTERN int aln_scheduler_alloc(aln_scheduler **scheduler, int n_threads); -EXTERN void aln_scheduler_free(aln_scheduler *n); - -EXTERN int queue_alloc(aln_elem_queue **queue, uint8_t is_allocator); -EXTERN void queue_free(aln_elem_queue *l); - -#undef CORETRALIGN_IMPORT -#undef EXTERN - -#endif diff --git a/lib/src/ensemble.c b/lib/src/ensemble.c index bfc2bc0..ec97d5a 100644 --- a/lib/src/ensemble.c +++ b/lib/src/ensemble.c @@ -11,8 +11,11 @@ #include "aln_wrap.h" #include "poar.h" #include "consensus_msa.h" +#include "anchor_consistency.h" +#include "msa_consistency.h" #include "kalign/kalign.h" +#include "kalign/kalign_config.h" #define ENSEMBLE_IMPORT #include "ensemble.h" @@ -45,36 +48,6 @@ static struct ensemble_params run_params[] = { }; #define N_RUN_PARAMS 12 -/* --------------------------------------------------------------------------- - * Helper: resolve_run_params - * - * Given base gap penalties and a run index, compute the run-specific - * gap-open, gap-extend, terminal-gap-extend, seed, and noise values. - * Run 0 always uses defaults (deterministic, no noise). - * ------------------------------------------------------------------------- */ -static void resolve_run_params(float base_gpo, float base_gpe, float base_tgpe, - int k, uint64_t seed, - float* out_gpo, float* out_gpe, float* out_tgpe, - uint64_t* out_seed, float* out_noise) -{ - if(k == 0){ - /* Run 0: default params, deterministic */ - *out_gpo = base_gpo; - *out_gpe = base_gpe; - *out_tgpe = base_tgpe; - *out_seed = 0; - *out_noise = 0.0f; - }else{ - /* Subsequent runs: independent per-penalty scaling + tree noise */ - struct ensemble_params ep = run_params[k % N_RUN_PARAMS]; - *out_gpo = base_gpo * ep.gpo_scale; - *out_gpe = base_gpe * ep.gpe_scale; - *out_tgpe = base_tgpe * ep.tgpe_scale; - *out_seed = seed + (uint64_t)k; - *out_noise = ep.noise; - } -} - /* --------------------------------------------------------------------------- * Helper: score_alignments * @@ -85,7 +58,11 @@ static void resolve_run_params(float base_gpo, float base_gpe, float base_tgpe, static int score_alignments(struct msa** alignments, struct poar_table* poar, int numseq, int n_runs, int quiet, - double** out_scores, int* out_best_k) + double** out_scores, int* out_best_k +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { struct pos_matrix* pm = NULL; double* scores = NULL; @@ -102,7 +79,11 @@ static int score_alignments(struct msa** alignments, } RUN(pos_matrix_from_msa(&pm, aln_seqs, numseq, alignments[k]->alnlen)); +#ifdef USE_THREADPOOL + RUN(score_alignment_poar(poar, pm, numseq, n_runs, &scores[k], pool)); +#else RUN(score_alignment_poar(poar, pm, numseq, n_runs, &scores[k])); +#endif if(!quiet){ LOG_MSG(" Run %d score: %.1f", k + 1, scores[k]); @@ -142,7 +123,11 @@ static int score_alignments(struct msa** alignments, static int build_consensus_from_poar(struct poar_table* poar, struct msa* msa, int numseq, int min_support, - struct msa** out_consensus) + struct msa** out_consensus +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { struct msa* consensus_msa = NULL; int* seq_lens = NULL; @@ -154,7 +139,11 @@ static int build_consensus_from_poar(struct poar_table* poar, seq_lens[i] = msa->sequences[i]->len; } +#ifdef USE_THREADPOOL + RUN(build_consensus(poar, seq_lens, numseq, min_support, consensus_msa, pool)); +#else RUN(build_consensus(poar, seq_lens, numseq, min_support, consensus_msa)); +#endif MFREE(seq_lens); *out_consensus = consensus_msa; @@ -196,7 +185,11 @@ static int copy_alignment_to_msa(struct msa* dst, struct msa* src, int numseq) * out_score. This avoids repeating the aln_seqs + pos_matrix pattern. * ------------------------------------------------------------------------- */ static int score_single_msa(struct msa* aln, struct poar_table* poar, - int numseq, int n_runs, double* out_score) + int numseq, int n_runs, double* out_score +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { struct pos_matrix* pm = NULL; char** aln_seqs = NULL; @@ -207,7 +200,11 @@ static int score_single_msa(struct msa* aln, struct poar_table* poar, } RUN(pos_matrix_from_msa(&pm, aln_seqs, numseq, aln->alnlen)); +#ifdef USE_THREADPOOL + RUN(score_alignment_poar(poar, pm, numseq, n_runs, out_score, pool)); +#else RUN(score_alignment_poar(poar, pm, numseq, n_runs, out_score)); +#endif pos_matrix_free(pm); MFREE(aln_seqs); @@ -218,39 +215,98 @@ static int score_single_msa(struct msa* aln, struct poar_table* poar, return FAIL; } +/* kalign_ensemble and kalign_ensemble_custom removed — + use kalign_align_full with per-run configs instead. */ + +/* ---- Parallel ensemble run support ---- */ +#ifdef USE_THREADPOOL +#include "threadpool/threadpool.h" + +struct ensemble_run_arg { + struct msa* copy; + const struct kalign_run_config* cfg; + int n_threads; + int error; +}; + +static void ensemble_run_task_fn(void* arg) +{ + struct ensemble_run_arg* ra = (struct ensemble_run_arg*)arg; + if(kalign_single_run(ra->copy, ra->cfg, ra->n_threads) != 0){ + ra->error = 1; + } +} +#endif + /* ======================================================================== */ -int kalign_ensemble(struct msa* msa, int n_threads, int type, - int n_runs, float gpo, float gpe, float tgpe, - uint64_t seed, int min_support, - const char* save_poar_path, - int refine, float dist_scale, float vsm_amax, - int realign, float use_seq_weights, - int consistency_anchors, float consistency_weight) +/* kalign_generate_ensemble_runs: expand base config into N diversified runs. + * + * IMPORTANT: base.gpo/gpe/tgpe must be resolved (non-sentinel) values. + * If they are -1.0 (sentinel), the scale factors will produce garbage. + * The caller should resolve sentinels via aln_param_init before calling this. + */ +int kalign_generate_ensemble_runs(const struct kalign_run_config* base, + int n_runs, uint64_t seed, + struct kalign_run_config* out) +{ + int k; + + ASSERT(base != NULL, "base config is NULL"); + ASSERT(out != NULL, "output array is NULL"); + ASSERT(n_runs >= 1, "n_runs must be >= 1"); + + for(k = 0; k < n_runs; k++){ + /* Start with a copy of the base config */ + out[k] = *base; + + if(k == 0){ + /* Run 0: base params, deterministic tree */ + out[k].tree_seed = 0; + out[k].tree_noise = 0.0f; + }else{ + /* Apply diversity table scale factors */ + struct ensemble_params ep = run_params[k % N_RUN_PARAMS]; + out[k].gpo = base->gpo * ep.gpo_scale; + out[k].gpe = base->gpe * ep.gpe_scale; + out[k].tgpe = base->tgpe * ep.tgpe_scale; + out[k].tree_seed = seed + (uint64_t)k; + out[k].tree_noise = ep.noise; + } + } + + return OK; +ERROR: + return FAIL; +} + +/* ======================================================================== */ +/* kalign_ensemble_from_configs: run ensemble alignment with per-run configs. + * + * This is the core ensemble implementation used by kalign_align_full. + * Each runs[k] is a fully-specified run configuration. + */ +int kalign_ensemble_from_configs(struct msa* msa, + const struct kalign_run_config* runs, + int n_runs, + const struct kalign_ensemble_config* ens, + int n_threads) { struct msa* copy = NULL; struct msa* consensus_msa = NULL; struct msa** alignments = NULL; struct poar_table* poar = NULL; struct pos_matrix* pm = NULL; - struct aln_param* ap = NULL; double* scores = NULL; int numseq; int k; int best_k = 0; int use_consensus = 0; - float base_gpo, base_gpe, base_tgpe; ASSERT(msa != NULL, "No MSA"); + ASSERT(runs != NULL, "No run configs"); ASSERT(n_runs >= 1, "n_runs must be >= 1"); - /* Seq_weights hurts ensemble performance (POAR consensus already - handles profile imbalance). Default to OFF in ensemble mode. */ - if(use_seq_weights < 0.0f){ - use_seq_weights = 0.0f; - } - - /* Essential input check + detect alphabet */ RUN(kalign_essential_input_check(msa, 0)); numseq = msa->numseq; @@ -261,211 +317,336 @@ int kalign_ensemble(struct msa* msa, int n_threads, int type, } START_TIMER(t_ensemble); - /* Resolve default gap penalties using aln_param_init. - We need to detect biotype first. */ if(msa->biotype == ALN_BIOTYPE_UNDEF){ RUN(detect_alphabet(msa)); } - /* Use aln_param_init to resolve defaults */ - RUN(aln_param_init(&ap, msa->biotype, n_threads, type, gpo, gpe, tgpe)); - base_gpo = ap->gpo; - base_gpe = ap->gpe; - base_tgpe = ap->tgpe; - aln_param_free(ap); - ap = NULL; - - /* Allocate POAR table and array to store completed alignments */ RUN(poar_table_alloc(&poar, numseq)); MMALLOC(alignments, sizeof(struct msa*) * n_runs); for(k = 0; k < n_runs; k++){ alignments[k] = NULL; } - /* Run N alignments, extract POARs, and keep each alignment */ - for(k = 0; k < n_runs; k++){ - float run_gpo, run_gpe, run_tgpe, run_noise; - uint64_t run_seed; + /* Phase timing instrumentation */ + DECLARE_TIMER(t_phase); - resolve_run_params(base_gpo, base_gpe, base_tgpe, k, seed, - &run_gpo, &run_gpe, &run_tgpe, - &run_seed, &run_noise); + /* Run N alignments concurrently — all runs share the one global + threadpool, giving the pool N× more tasks to keep workers busy. + POAR extraction is sequential (sorted insert not thread-safe). */ + START_TIMER(t_phase); +#ifdef USE_THREADPOOL + if(msa->pool != NULL && n_runs > 1){ + struct ensemble_run_arg* run_args = NULL; + MMALLOC(run_args, sizeof(struct ensemble_run_arg) * n_runs); - /* Deep-copy MSA */ - copy = NULL; - RUN(msa_cpy(©, msa)); - copy->quiet = 1; + for(k = 0; k < n_runs; k++){ + run_args[k].copy = NULL; + RUN(msa_cpy(&run_args[k].copy, msa)); + run_args[k].copy->quiet = 1; + run_args[k].copy->pool = msa->pool; + run_args[k].cfg = &runs[k]; + run_args[k].n_threads = n_threads; + run_args[k].error = 0; - if(!msa->quiet){ - LOG_MSG(" Run %d/%d (gpo=%.1f gpe=%.1f tgpe=%.1f noise=%.2f)", - k + 1, n_runs, run_gpo, run_gpe, run_tgpe, run_noise); + if(!msa->quiet){ + LOG_MSG(" Run %d/%d (gpo=%.1f gpe=%.1f tgpe=%.1f noise=%.2f)", + k + 1, n_runs, + runs[k].gpo, runs[k].gpe, runs[k].tgpe, + runs[k].tree_noise); + } } - /* Run alignment */ - if(realign > 0){ - RUN(kalign_run_realign(copy, n_threads, type, - run_gpo, run_gpe, run_tgpe, - refine, 0, - dist_scale, vsm_amax, - realign, use_seq_weights, - consistency_anchors, consistency_weight)); - }else{ - RUN(kalign_run_seeded(copy, n_threads, type, - run_gpo, run_gpe, run_tgpe, - refine, 0, - run_seed, run_noise, - dist_scale, vsm_amax, - use_seq_weights, - consistency_anchors, consistency_weight)); + /* Fork all runs into the shared pool */ + { + tp_group_t *g = tp_group_create(msa->pool); + for(k = 0; k < n_runs; k++){ + tp_group_submit(g, ensemble_run_task_fn, &run_args[k]); + } + tp_group_wait(g); + tp_group_destroy(g); } - /* Extract POARs from the finalized alignment */ - char** aln_seqs = NULL; - MMALLOC(aln_seqs, sizeof(char*) * numseq); - for(int i = 0; i < numseq; i++){ - aln_seqs[i] = copy->sequences[i]->seq; + /* Check for errors and collect results */ + for(k = 0; k < n_runs; k++){ + if(run_args[k].error){ + for(int j = 0; j < n_runs; j++){ + if(run_args[j].copy) kalign_free_msa(run_args[j].copy); + } + MFREE(run_args); + ERROR_MSG("Ensemble run %d failed", k + 1); + } + alignments[k] = run_args[k].copy; + run_args[k].copy = NULL; } + MFREE(run_args); - RUN(pos_matrix_from_msa(&pm, aln_seqs, numseq, copy->alnlen)); - RUN(extract_poars(poar, pm, k)); + /* Extract POARs sequentially */ + for(k = 0; k < n_runs; k++){ + char** aln_seqs = NULL; + MMALLOC(aln_seqs, sizeof(char*) * numseq); + for(int i = 0; i < numseq; i++){ + aln_seqs[i] = alignments[k]->sequences[i]->seq; + } + RUN(pos_matrix_from_msa(&pm, aln_seqs, numseq, alignments[k]->alnlen)); + { +#ifdef USE_THREADPOOL + int _ep_ret = extract_poars(poar, pm, k, msa->pool); +#else + int _ep_ret = extract_poars(poar, pm, k); +#endif + if(_ep_ret != OK) goto ERROR; + } + pos_matrix_free(pm); + pm = NULL; + MFREE(aln_seqs); + } + }else +#endif + { + /* Sequential fallback (no threadpool or single run) */ + for(k = 0; k < n_runs; k++){ + copy = NULL; + RUN(msa_cpy(©, msa)); + copy->quiet = 1; +#ifdef USE_THREADPOOL + copy->pool = msa->pool; +#endif + if(!msa->quiet){ + LOG_MSG(" Run %d/%d (gpo=%.1f gpe=%.1f tgpe=%.1f noise=%.2f)", + k + 1, n_runs, + runs[k].gpo, runs[k].gpe, runs[k].tgpe, + runs[k].tree_noise); + } + RUN(kalign_single_run(copy, &runs[k], n_threads)); - pos_matrix_free(pm); - pm = NULL; - MFREE(aln_seqs); + char** aln_seqs = NULL; + MMALLOC(aln_seqs, sizeof(char*) * numseq); + for(int i = 0; i < numseq; i++){ + aln_seqs[i] = copy->sequences[i]->seq; + } + RUN(pos_matrix_from_msa(&pm, aln_seqs, numseq, copy->alnlen)); + { +#ifdef USE_THREADPOOL + int _ep_ret = extract_poars(poar, pm, k, msa->pool); +#else + int _ep_ret = extract_poars(poar, pm, k); +#endif + if(_ep_ret != OK) goto ERROR; + } + pos_matrix_free(pm); + pm = NULL; + MFREE(aln_seqs); - /* Keep this alignment for scoring later */ - alignments[k] = copy; - copy = NULL; + alignments[k] = copy; + copy = NULL; + } } + STOP_TIMER(t_phase); + if(!msa->quiet){ LOG_MSG(" [time] alignment runs + POAR extraction: "); GET_TIMING(t_phase); } + /* Score all alignments and select the best */ + START_TIMER(t_phase); + #ifdef USE_THREADPOOL + RUN(score_alignments(alignments, poar, numseq, n_runs, msa->quiet, + &scores, &best_k, msa->pool)); +#else RUN(score_alignments(alignments, poar, numseq, n_runs, msa->quiet, &scores, &best_k)); +#endif if(!msa->quiet){ LOG_MSG(" Selected run %d (score=%.1f)", best_k + 1, scores[best_k]); } - /* Save POAR table if requested */ - if(save_poar_path != NULL){ - RUN(poar_table_write(poar, save_poar_path)); - if(!msa->quiet){ - LOG_MSG(" Saved POAR table to %s", save_poar_path); - } - } + STOP_TIMER(t_phase); + if(!msa->quiet){ LOG_MSG(" [time] scoring: "); GET_TIMING(t_phase); } + + /* Determine merge strategy */ + START_TIMER(t_phase); + int min_support = (ens != NULL) ? ens->min_support : 0; + int use_consistency_merge = (ens != NULL) ? ens->consistency_merge : 0; + + if(use_consistency_merge){ + /* ---- POAR consistency merge path ---- + * Use the already-built POAR table as a source of pairwise + * residue consistency scores for a fresh progressive alignment. + * Uses best_k's gap penalties and matrix. */ + float cm_weight = (ens != NULL) ? ens->consistency_merge_weight : 2.0f; + struct poar_consistency_ctx poar_ctx; + poar_ctx.poar = poar; + poar_ctx.n_runs = n_runs; + poar_ctx.weight = cm_weight; + + copy = NULL; + RUN(msa_cpy(©, msa)); + copy->quiet = msa->quiet ? 1 : 0; + + /* Attach POAR consistency context — the progressive alignment + will pick it up via msa->poar_consistency in aln_run.c */ + copy->poar_consistency = &poar_ctx; - /* When min_support > 0: explicit consensus threshold, skip selection. - When min_support == 0: auto behavior (selection vs consensus). */ - if(min_support > 0){ - /* Explicit consensus: force consensus path */ - RUN(build_consensus_from_poar(poar, msa, numseq, min_support, - &consensus_msa)); - use_consensus = 1; if(!msa->quiet){ - LOG_MSG(" Using consensus alignment (min_support=%d)", min_support); + LOG_MSG(" Consistency merge (weight=%.1f) using run %d params", + cm_weight, best_k + 1); } - }else{ - /* Try consensus approach: build a new alignment from POAR table. - This can combine correct pairs from multiple runs, potentially - outperforming any single run. */ - double consensus_score = 0.0; - int min_sup = (n_runs + 2) / 3; - if(min_sup < 2) min_sup = 2; - RUN(build_consensus_from_poar(poar, msa, numseq, min_sup, - &consensus_msa)); + /* Run a fresh progressive alignment with best_k's params. + No additional anchor consistency or realign — the POAR + consistency signal is the main guide. */ + { + struct kalign_run_config cm_cfg = runs[best_k]; + cm_cfg.refine = KALIGN_REFINE_NONE; + cm_cfg.tree_seed = 0; + cm_cfg.tree_noise = 0.0f; + cm_cfg.seq_weights = 0.0f; + cm_cfg.consistency_anchors = 0; + cm_cfg.realign = 0; +#ifdef USE_THREADPOOL + copy->pool = msa->pool; +#endif + RUN(kalign_single_run(copy, &cm_cfg, n_threads)); + } - /* Score consensus against POAR table */ - RUN(score_single_msa(consensus_msa, poar, numseq, n_runs, - &consensus_score)); + /* Clear the non-owning pointer before freeing the copy */ + copy->poar_consistency = NULL; - if(!msa->quiet){ - LOG_MSG(" Consensus score: %.1f (selection: %.1f)", - consensus_score, scores[best_k]); - } + RUN(copy_alignment_to_msa(msa, copy, numseq)); + kalign_free_msa(copy); + copy = NULL; - if(consensus_score > scores[best_k]){ + }else{ + /* ---- POAR consensus / selection path (existing) ---- */ + + if(min_support > 0){ + #ifdef USE_THREADPOOL + RUN(build_consensus_from_poar(poar, msa, numseq, min_support, + &consensus_msa, msa->pool)); + #else + RUN(build_consensus_from_poar(poar, msa, numseq, min_support, + &consensus_msa)); + #endif use_consensus = 1; if(!msa->quiet){ - LOG_MSG(" Using consensus alignment"); + LOG_MSG(" Using consensus alignment (min_support=%d)", min_support); } }else{ - kalign_free_msa(consensus_msa); - consensus_msa = NULL; + double consensus_score = 0.0; + int min_sup = (n_runs + 2) / 3; + if(min_sup < 2) min_sup = 2; + + #ifdef USE_THREADPOOL + RUN(build_consensus_from_poar(poar, msa, numseq, min_sup, + &consensus_msa, msa->pool)); + #else + RUN(build_consensus_from_poar(poar, msa, numseq, min_sup, + &consensus_msa)); + #endif + + #ifdef USE_THREADPOOL + RUN(score_single_msa(consensus_msa, poar, numseq, n_runs, + &consensus_score, msa->pool)); + #else + RUN(score_single_msa(consensus_msa, poar, numseq, n_runs, + &consensus_score)); + #endif + if(!msa->quiet){ - LOG_MSG(" Keeping selection winner"); + LOG_MSG(" Consensus score: %.1f (selection: %.1f)", + consensus_score, scores[best_k]); + } + + if(consensus_score > scores[best_k]){ + use_consensus = 1; + if(!msa->quiet){ + LOG_MSG(" Using consensus alignment"); + } + }else{ + kalign_free_msa(consensus_msa); + consensus_msa = NULL; + if(!msa->quiet){ + LOG_MSG(" Keeping selection winner"); + } } } - } - /* Post-selection refinement: only when using selection (not consensus), - re-run the winner with REFINE_CONFIDENT and keep if it scores higher. */ - if(!use_consensus){ - float ref_gpo, ref_gpe, ref_tgpe, ref_noise; - uint64_t ref_seed; + /* Post-selection refinement: re-run the winner with REFINE_CONFIDENT */ + if(!use_consensus){ + struct kalign_run_config ref_cfg = runs[best_k]; + ref_cfg.refine = KALIGN_REFINE_CONFIDENT; - resolve_run_params(base_gpo, base_gpe, base_tgpe, best_k, seed, - &ref_gpo, &ref_gpe, &ref_tgpe, - &ref_seed, &ref_noise); + copy = NULL; + RUN(msa_cpy(©, msa)); + copy->quiet = 1; +#ifdef USE_THREADPOOL + copy->pool = msa->pool; +#endif - copy = NULL; - RUN(msa_cpy(©, msa)); - copy->quiet = 1; + if(!msa->quiet){ + LOG_MSG(" Refining run %d...", best_k + 1); + } - if(!msa->quiet){ - LOG_MSG(" Refining run %d...", best_k + 1); - } + RUN(kalign_single_run(copy, &ref_cfg, n_threads)); - RUN(kalign_run_seeded(copy, n_threads, type, - ref_gpo, ref_gpe, ref_tgpe, - KALIGN_REFINE_CONFIDENT, 0, - ref_seed, ref_noise, - dist_scale, vsm_amax, - use_seq_weights, - consistency_anchors, consistency_weight)); + double refined_score = 0.0; + #ifdef USE_THREADPOOL + RUN(score_single_msa(copy, poar, numseq, n_runs, + &refined_score, msa->pool)); + #else + RUN(score_single_msa(copy, poar, numseq, n_runs, + &refined_score)); + #endif - /* Score the refined alignment against the same POAR table */ - double refined_score = 0.0; - RUN(score_single_msa(copy, poar, numseq, n_runs, - &refined_score)); + if(!msa->quiet){ + LOG_MSG(" Refined score: %.1f (was %.1f)", + refined_score, scores[best_k]); + } - if(!msa->quiet){ - LOG_MSG(" Refined score: %.1f (was %.1f)", - refined_score, scores[best_k]); + if(refined_score > scores[best_k]){ + kalign_free_msa(alignments[best_k]); + alignments[best_k] = copy; + copy = NULL; + if(!msa->quiet){ + LOG_MSG(" Using refined alignment"); + } + }else{ + kalign_free_msa(copy); + copy = NULL; + if(!msa->quiet){ + LOG_MSG(" Keeping original alignment"); + } + } } - if(refined_score > scores[best_k]){ - kalign_free_msa(alignments[best_k]); - alignments[best_k] = copy; - copy = NULL; - if(!msa->quiet){ - LOG_MSG(" Using refined alignment"); - } + MFREE(scores); + scores = NULL; + + if(use_consensus){ + RUN(copy_alignment_to_msa(msa, consensus_msa, numseq)); + kalign_free_msa(consensus_msa); + consensus_msa = NULL; }else{ - kalign_free_msa(copy); - copy = NULL; - if(!msa->quiet){ - LOG_MSG(" Keeping original alignment"); - } + RUN(copy_alignment_to_msa(msa, alignments[best_k], numseq)); } } - MFREE(scores); - scores = NULL; - - /* Copy the winning alignment back into the original MSA */ - if(use_consensus){ - RUN(copy_alignment_to_msa(msa, consensus_msa, numseq)); - kalign_free_msa(consensus_msa); - consensus_msa = NULL; - }else{ - RUN(copy_alignment_to_msa(msa, alignments[best_k], numseq)); + if(scores){ + MFREE(scores); + scores = NULL; } - /* Compute per-residue and per-column confidence from POAR table */ + STOP_TIMER(t_phase); + if(!msa->quiet){ LOG_MSG(" [time] consensus/selection: "); GET_TIMING(t_phase); } + + START_TIMER(t_phase); + #ifdef USE_THREADPOOL + RUN(compute_residue_confidence(poar, msa, msa->pool)); +#else RUN(compute_residue_confidence(poar, msa)); +#endif + STOP_TIMER(t_phase); + if(!msa->quiet){ LOG_MSG(" [time] confidence: "); GET_TIMING(t_phase); } - /* Sort back to original rank order */ RUN(msa_sort_rank(msa)); STOP_TIMER(t_ensemble); @@ -473,8 +654,8 @@ int kalign_ensemble(struct msa* msa, int n_threads, int type, GET_TIMING(t_ensemble); } DESTROY_TIMER(t_ensemble); + DESTROY_TIMER(t_phase); - /* Free all alignments */ for(k = 0; k < n_runs; k++){ if(alignments[k]) kalign_free_msa(alignments[k]); } @@ -493,10 +674,11 @@ int kalign_ensemble(struct msa* msa, int n_threads, int type, } poar_table_free(poar); if(scores) MFREE(scores); - if(ap) aln_param_free(ap); return FAIL; } +/* ======================================================================== */ + int kalign_consensus_from_poar(struct msa* msa, const char* poar_path, int min_support) @@ -521,8 +703,18 @@ int kalign_consensus_from_poar(struct msa* msa, } /* Build consensus at given min_support threshold */ - RUN(build_consensus_from_poar(poar, msa, numseq, min_support, - &consensus_msa)); + #ifdef USE_THREADPOOL + RUN(build_consensus_from_poar(poar, msa, numseq, min_support, + &consensus_msa, msa->pool)); +#else + #ifdef USE_THREADPOOL + RUN(build_consensus_from_poar(poar, msa, numseq, min_support, + &consensus_msa, msa->pool)); + #else + RUN(build_consensus_from_poar(poar, msa, numseq, min_support, + &consensus_msa)); + #endif +#endif /* Copy consensus alignment back into original MSA */ RUN(copy_alignment_to_msa(msa, consensus_msa, numseq)); @@ -530,7 +722,11 @@ int kalign_consensus_from_poar(struct msa* msa, consensus_msa = NULL; /* Compute per-residue and per-column confidence */ + #ifdef USE_THREADPOOL + RUN(compute_residue_confidence(poar, msa, msa->pool)); +#else RUN(compute_residue_confidence(poar, msa)); +#endif RUN(msa_sort_rank(msa)); diff --git a/lib/src/ensemble.h b/lib/src/ensemble.h index 1cc475e..fa5a94a 100644 --- a/lib/src/ensemble.h +++ b/lib/src/ensemble.h @@ -2,6 +2,7 @@ #define ENSEMBLE_H #include +#include #ifdef ENSEMBLE_IMPORT #define EXTERN @@ -15,13 +16,15 @@ struct msa; -EXTERN int kalign_ensemble(struct msa* msa, int n_threads, int type, - int n_runs, float gpo, float gpe, float tgpe, - uint64_t seed, int min_support, - const char* save_poar_path, - int refine, float dist_scale, float vsm_amax, - int realign, float use_seq_weights, - int consistency_anchors, float consistency_weight); +EXTERN int kalign_ensemble_from_configs(struct msa* msa, + const struct kalign_run_config* runs, + int n_runs, + const struct kalign_ensemble_config* ens, + int n_threads); + +EXTERN int kalign_generate_ensemble_runs(const struct kalign_run_config* base, + int n_runs, uint64_t seed, + struct kalign_run_config* out); EXTERN int kalign_consensus_from_poar(struct msa* msa, const char* poar_path, diff --git a/lib/src/euclidean_dist.c b/lib/src/euclidean_dist.c index d69af7f..b2e76c7 100644 --- a/lib/src/euclidean_dist.c +++ b/lib/src/euclidean_dist.c @@ -33,7 +33,9 @@ int main(void) float** mat = NULL; double r; float d1; +#ifdef HAVE_AVX2 float d2; +#endif int i,j,c; int max_iter = 10; int num_element = 128; diff --git a/lib/src/mod_tldevel.h b/lib/src/mod_tldevel.h deleted file mode 100644 index 66c17bd..0000000 --- a/lib/src/mod_tldevel.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef MOD_TLDEVEL_H -#define MOD_TLDEVEL_H - - -#include "tldevel.h" -#include "tlrng.h" -#include "tlmisc.h" -#include "esl_stopwatch.h" - -#endif diff --git a/lib/src/msa_alloc.c b/lib/src/msa_alloc.c index bde353c..02e86ae 100644 --- a/lib/src/msa_alloc.c +++ b/lib/src/msa_alloc.c @@ -27,7 +27,7 @@ int alloc_msa(struct msa** msa, int numseq) m->sip = NULL; m->nsip = NULL; m->consistency_table = NULL; - + m->poar_consistency = NULL; MMALLOC(m->sequences, sizeof(struct msa_seq*) * m->alloc_numseq); diff --git a/lib/src/msa_check.c b/lib/src/msa_check.c index fe49e50..3846f2f 100644 --- a/lib/src/msa_check.c +++ b/lib/src/msa_check.c @@ -22,47 +22,8 @@ struct sort_struct_name_chksum{ static int GCGchecksum(char *seq, int len); static int sort_by_name(const void *a, const void *b); static int sort_by_chksum(const void *a, const void *b); -static int sort_by_both(const void *a, const void *b); - int sort_seq_by_len(const void *a, const void *b); -int kalign_sort_msa(struct msa *msa) -{ - struct sort_struct_name_chksum** a = NULL; - - MMALLOC(a, sizeof(struct sort_struct_name_chksum *) * msa->numseq); - - for(int i = 0; i < msa->numseq;i++){ - a[i] = NULL; - MMALLOC(a[i], sizeof(struct sort_struct_name_chksum)); - a[i]->seq = msa->sequences[i]; - a[i]->name = &msa->sequences[i]->name; - a[i]->chksum = GCGchecksum(msa->sequences[i]->seq, msa->sequences[i]->len); - a[i]->action = 0; - } - - qsort(a, msa->numseq, sizeof(struct sort_struct*),sort_by_both); - - for(int i = 0; i < msa->numseq;i++){ - msa->sequences[i] = a[i]->seq; - } - - for(int i = 0; i < msa->numseq;i++){ - MFREE(a[i]); - } - MFREE(a); - return OK; -ERROR: - if(a){ - for(int i = 0; i < msa->numseq;i++){ - MFREE(a[i]); - } - MFREE(a); - } - return FAIL; -} - - int kalign_essential_input_check(struct msa *msa, int exit_on_error) { int problem_len0 = 0; @@ -244,23 +205,6 @@ int kalign_check_msa(struct msa* msa, int exit_on_error) return FAIL; } -int sort_by_both(const void *a, const void *b) -{ - struct sort_struct_name_chksum* const *one = a; - struct sort_struct_name_chksum* const *two = b; - - if(strncmp(*(*one)->name, *(*two)->name,MSA_NAME_LEN) < 0){ - return -1; - }else if(strncmp(*(*one)->name, *(*two)->name,MSA_NAME_LEN) == 0 ){ - if((*one)->chksum > (*two)->chksum){ - return -1; - }else{ - return 1; - } - }else{ - return 1; - } -} int sort_by_name(const void *a, const void *b) { struct sort_struct_name_chksum* const *one = a; diff --git a/lib/src/msa_check.h b/lib/src/msa_check.h index c381a54..39e61c9 100644 --- a/lib/src/msa_check.h +++ b/lib/src/msa_check.h @@ -15,7 +15,6 @@ struct msa; EXTERN int kalign_essential_input_check(struct msa *msa, int exit_on_error); EXTERN int kalign_check_msa(struct msa* msa, int exit_on_error); -EXTERN int kalign_sort_msa(struct msa *msa); #undef MSA_CHECK_IMPORT #undef EXTERN diff --git a/lib/src/msa_cmp.c b/lib/src/msa_cmp.c index 23fba02..73e72a8 100644 --- a/lib/src/msa_cmp.c +++ b/lib/src/msa_cmp.c @@ -1,6 +1,7 @@ #include "tldevel.h" #include +#include #include "msa_struct.h" #include "msa_check.h" #include "msa_op.h" @@ -26,6 +27,95 @@ struct detailed_pair_stats { int64_t common_all; /* all matches → for precision */ }; +/* Sort key for robust sequence matching in MSA comparison. + Key = name + '\0' + ungapped_sequence, so sequences are sorted first + by name, then by biological content. This guarantees identical ordering + of ref and test MSAs regardless of gap patterns or duplicate names. */ +struct cmp_sort_entry { + struct msa_seq* seq; + char* key; + int key_len; +}; + +static int cmp_sort_by_key(const void *a, const void *b) +{ + const struct cmp_sort_entry* const *ea = a; + const struct cmp_sort_entry* const *eb = b; + int min_len = (*ea)->key_len < (*eb)->key_len ? (*ea)->key_len : (*eb)->key_len; + int r = memcmp((*ea)->key, (*eb)->key, min_len); + if(r != 0) return r; + return (*ea)->key_len - (*eb)->key_len; +} + +/* Sort an MSA's sequences by name + ungapped sequence content. + Works on finalized alignments (seq->seq may contain gaps) and on + raw unaligned sequences alike. Both ref and test get identical + ordering because the sort key ignores gap characters entirely. */ +static int sort_msa_for_comparison(struct msa* msa) +{ + struct cmp_sort_entry** entries = NULL; + int i, j; + + MMALLOC(entries, sizeof(struct cmp_sort_entry*) * msa->numseq); + for(i = 0; i < msa->numseq; i++){ + entries[i] = NULL; + MMALLOC(entries[i], sizeof(struct cmp_sort_entry)); + entries[i]->seq = msa->sequences[i]; + entries[i]->key = NULL; + + /* Compute key length: name_len + 1 (separator) + ungapped_len */ + int name_len = strnlen(msa->sequences[i]->name, MSA_NAME_LEN); + int seq_len = 0; + /* Count ungapped characters — works whether seq has gaps or not */ + int scan_len = (msa->alnlen > 0) ? msa->alnlen : msa->sequences[i]->len; + for(j = 0; j < scan_len; j++){ + if(isalpha((int)msa->sequences[i]->seq[j])){ + seq_len++; + } + } + + entries[i]->key_len = name_len + 1 + seq_len; + MMALLOC(entries[i]->key, sizeof(char) * (entries[i]->key_len + 1)); + + /* Build key: name + '\0' separator + ungapped sequence */ + memcpy(entries[i]->key, msa->sequences[i]->name, name_len); + entries[i]->key[name_len] = '\0'; + int p = name_len + 1; + for(j = 0; j < scan_len; j++){ + char c = msa->sequences[i]->seq[j]; + if(isalpha((int)c)){ + entries[i]->key[p] = c; + p++; + } + } + entries[i]->key[p] = '\0'; + } + + qsort(entries, msa->numseq, sizeof(struct cmp_sort_entry*), cmp_sort_by_key); + + for(i = 0; i < msa->numseq; i++){ + msa->sequences[i] = entries[i]->seq; + } + + for(i = 0; i < msa->numseq; i++){ + MFREE(entries[i]->key); + MFREE(entries[i]); + } + MFREE(entries); + return OK; +ERROR: + if(entries){ + for(i = 0; i < msa->numseq; i++){ + if(entries[i]){ + MFREE(entries[i]->key); + MFREE(entries[i]); + } + } + MFREE(entries); + } + return FAIL; +} + static int compare_pair(char *seq1A, char *seq2A, char *seq1B, char *seq2B, int len_a, int len_b, struct cmp_stats *stat); @@ -43,17 +133,22 @@ int kalign_msa_compare(struct msa *r, struct msa *t, float *score) if(r->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(r); + RUN(finalise_alignment(r)); } if(t->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(t); + RUN(finalise_alignment(t)); } - RUN(kalign_check_msa(r,1)); - RUN(kalign_check_msa(t,1)); - kalign_sort_msa(r); - kalign_sort_msa(t); + if(r->alnlen == 0 && r->numseq > 0){ + r->alnlen = r->sequences[0]->len; + } + if(t->alnlen == 0 && t->numseq > 0){ + t->alnlen = t->sequences[0]->len; + } + + RUN(sort_msa_for_comparison(r)); + RUN(sort_msa_for_comparison(t)); MMALLOC(stat, sizeof(struct cmp_stats)); stat->identical_gaps = 0; @@ -413,16 +508,28 @@ int kalign_msa_compare_detailed(struct msa *r, struct msa *t, ASSERT(out != NULL, "No output struct"); if(r->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(r); + RUN(finalise_alignment(r)); } if(t->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(t); + RUN(finalise_alignment(t)); + } + + /* Handle references read from file that had no gaps: + detect_aligned() sets ALN_STATUS_UNKNOWN when all sequences are + the same length with no gap characters. In that case alnlen is + still 0 but the sequences are stored verbatim in seq and the + alignment length equals the sequence length. */ + if(r->alnlen == 0 && r->numseq > 0){ + r->alnlen = r->sequences[0]->len; + } + if(t->alnlen == 0 && t->numseq > 0){ + t->alnlen = t->sequences[0]->len; } - RUN(kalign_check_msa(r, 1)); - RUN(kalign_check_msa(t, 1)); - kalign_sort_msa(r); - kalign_sort_msa(t); + RUN(sort_msa_for_comparison(r)); + RUN(sort_msa_for_comparison(t)); + + ASSERT(r->alnlen > 0, "Reference alignment has length 0"); /* Build scored column mask from reference alignment */ MMALLOC(scored_cols, sizeof(int) * r->alnlen); @@ -460,16 +567,21 @@ int kalign_msa_compare_with_mask(struct msa *r, struct msa *t, ASSERT(out != NULL, "No output struct"); if(r->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(r); + RUN(finalise_alignment(r)); } if(t->aligned == ALN_STATUS_ALIGNED){ - finalise_alignment(t); + RUN(finalise_alignment(t)); + } + + if(r->alnlen == 0 && r->numseq > 0){ + r->alnlen = r->sequences[0]->len; + } + if(t->alnlen == 0 && t->numseq > 0){ + t->alnlen = t->sequences[0]->len; } - RUN(kalign_check_msa(r, 1)); - RUN(kalign_check_msa(t, 1)); - kalign_sort_msa(r); - kalign_sort_msa(t); + RUN(sort_msa_for_comparison(r)); + RUN(sort_msa_for_comparison(t)); ASSERT(n_cols == r->alnlen, "Mask length (%d) != reference alignment length (%d)", diff --git a/lib/src/msa_consistency.c b/lib/src/msa_consistency.c new file mode 100644 index 0000000..7481bbe --- /dev/null +++ b/lib/src/msa_consistency.c @@ -0,0 +1,185 @@ +#include "tldevel.h" + +#include "msa_struct.h" +#include "anchor_consistency.h" /* for sparse_bonus, sparse_bonus_free */ +#include "poar.h" + +#define MSA_CONSISTENCY_IMPORT +#include "msa_consistency.h" + +/* Flat index for pair (i,j) where i < j. + Duplicated from poar.c (static there). */ +static inline int poar_pair_idx(int i, int j, int numseq) +{ + return i * numseq - (i * (i + 1)) / 2 + (j - i - 1); +} + +/* Build ungapped_pos -> DP_position map for a member sequence. + * Walks gaps[] to find the DP column for each ungapped residue. + * Caller must free the returned array. */ +static int build_ungapped_to_dp(struct msa* msa, int seq_idx, + int** map_out, int* ungapped_len_out) +{ + int* gaps = msa->sequences[seq_idx]->gaps; + int seq_len = msa->sequences[seq_idx]->len; + int* map = NULL; + int dp, p, g; + + if(seq_len == 0){ + *map_out = NULL; + *ungapped_len_out = 0; + return OK; + } + + MMALLOC(map, sizeof(int) * seq_len); + + dp = 0; + for(p = 0; p <= seq_len; p++){ + for(g = 0; g < gaps[p]; g++){ + dp++; + } + if(p < seq_len){ + map[p] = dp; + dp++; + } + } + + *map_out = map; + *ungapped_len_out = seq_len; + return OK; +ERROR: + if(map) MFREE(map); + *map_out = NULL; + *ungapped_len_out = 0; + return FAIL; +} + +#define POAR_BONUS_K 16 /* max distinct target positions per DP row */ + +int poar_consistency_get_bonus(struct poar_consistency_ctx* ctx, + struct msa* msa, + int node_a, int len_a, + int node_b, int len_b, + struct sparse_bonus** bonus_out) +{ + struct sparse_bonus* sb = NULL; + int* map_a = NULL; + int* map_b = NULL; + int K = POAR_BONUS_K; + int numseq = ctx->poar->numseq; + int n_members_a = msa->nsip[node_a]; + int n_members_b = msa->nsip[node_b]; + int* members_a = msa->sip[node_a]; + int* members_b = msa->sip[node_b]; + float denom = (float)n_members_a * (float)n_members_b * (float)ctx->n_runs; + float pair_weight = ctx->weight / denom; + int ma, mb, e, i; + + /* Allocate sparse bonus */ + MMALLOC(sb, sizeof(struct sparse_bonus)); + sb->cols = NULL; + sb->vals = NULL; + sb->n_rows = len_a; + sb->K = K; + + MMALLOC(sb->cols, sizeof(int) * len_a * K); + MMALLOC(sb->vals, sizeof(float) * len_a * K); + for(i = 0; i < len_a * K; i++){ + sb->cols[i] = -1; + sb->vals[i] = 0.0f; + } + + /* For each pair of member sequences, look up POAR entries */ + for(ma = 0; ma < n_members_a; ma++){ + int sa = members_a[ma]; + int sa_len = 0; + + RUN(build_ungapped_to_dp(msa, sa, &map_a, &sa_len)); + + for(mb = 0; mb < n_members_b; mb++){ + int sb_seq = members_b[mb]; + int sb_len = 0; + int lo, hi; + int pidx; + struct poar_pair* pp; + int swapped; + + if(sa == sb_seq) continue; + + RUN(build_ungapped_to_dp(msa, sb_seq, &map_b, &sb_len)); + + /* POAR pairs are stored with lo < hi */ + if(sa < sb_seq){ + lo = sa; hi = sb_seq; + swapped = 0; + }else{ + lo = sb_seq; hi = sa; + swapped = 1; + } + + pidx = poar_pair_idx(lo, hi, numseq); + pp = ctx->poar->pairs[pidx]; + + if(pp != NULL){ + for(e = 0; e < pp->n_entries; e++){ + uint32_t key = pp->entries[e].key; + uint32_t support = pp->entries[e].support; + int pos_lo = (int)(key >> 20); + int pos_hi = (int)(key & 0xFFFFF); + int pos_sa, pos_sb; + int dp_row, dp_col; + int count, base, slot, s; + float val; + + if(swapped){ + pos_sa = pos_hi; + pos_sb = pos_lo; + }else{ + pos_sa = pos_lo; + pos_sb = pos_hi; + } + + if(pos_sa >= sa_len || pos_sb >= sb_len) continue; + if(map_a == NULL || map_b == NULL) continue; + + dp_row = map_a[pos_sa]; + dp_col = map_b[pos_sb]; + + if(dp_row >= len_a || dp_col >= len_b) continue; + + count = __builtin_popcount(support); + val = pair_weight * (float)count; + + /* Insert into sparse bonus */ + base = dp_row * K; + slot = -1; + for(s = 0; s < K; s++){ + if(sb->cols[base + s] == dp_col){ + slot = s; break; + } + if(sb->cols[base + s] < 0){ + slot = s; break; + } + } + if(slot >= 0){ + sb->vals[base + slot] += val; + sb->cols[base + slot] = dp_col; + } + } + } + + if(map_b){ MFREE(map_b); map_b = NULL; } + } + + if(map_a){ MFREE(map_a); map_a = NULL; } + } + + *bonus_out = sb; + return OK; +ERROR: + sparse_bonus_free(sb); + if(map_a) MFREE(map_a); + if(map_b) MFREE(map_b); + *bonus_out = NULL; + return FAIL; +} diff --git a/lib/src/msa_consistency.h b/lib/src/msa_consistency.h new file mode 100644 index 0000000..2f4869e --- /dev/null +++ b/lib/src/msa_consistency.h @@ -0,0 +1,46 @@ +#ifndef MSA_CONSISTENCY_H +#define MSA_CONSISTENCY_H + +#ifdef MSA_CONSISTENCY_IMPORT +#define EXTERN +#else +#ifdef __cplusplus +#define EXTERN extern "C" +#else +#define EXTERN extern +#endif +#endif + +struct msa; +struct poar_table; +struct sparse_bonus; + +/* Context for POAR-based consistency scoring. + * Stored as a non-owning reference in msa->poar_consistency. */ +struct poar_consistency_ctx { + struct poar_table* poar; /* non-owning reference */ + int n_runs; + float weight; +}; + +/* Compute consistency bonus for a pair of tree nodes from POAR data. + * + * For each member sequence pair (sa in node_a, sb in node_b), looks up + * the POAR entries to find how often each residue pair was co-aligned + * across the N ensemble runs. Maps ungapped positions to DP positions + * using the current gap structure and accumulates bonuses into a + * sparse_bonus matrix. + * + * node_a/node_b: tree node indices (leaf = sequence index) + * len_a/len_b: DP dimensions (profile widths) + */ +EXTERN int poar_consistency_get_bonus(struct poar_consistency_ctx* ctx, + struct msa* msa, + int node_a, int len_a, + int node_b, int len_b, + struct sparse_bonus** bonus_out); + +#undef MSA_CONSISTENCY_IMPORT +#undef EXTERN + +#endif diff --git a/lib/src/msa_io.c b/lib/src/msa_io.c index a619bf4..fba3438 100644 --- a/lib/src/msa_io.c +++ b/lib/src/msa_io.c @@ -173,6 +173,70 @@ int kalign_read_input(char* infile, struct msa** msa, int quiet) return FAIL; } +/* Like kalign_read_input but allows 1 sequence (for --add mode). */ +int kalign_read_sequences(char* infile, struct msa** msa, int quiet) +{ + /* Read normally but without the >= 2 check */ + struct msa* backup = *msa; + *msa = NULL; + + /* Use the same read logic but skip check_for_sequences. + We replicate the core of kalign_read_input here. */ + struct in_buffer* b = NULL; + struct msa* m = NULL; + int type; + int i, j; + + if(infile && !my_file_exists(infile)){ + ERROR_MSG("File: %s does not exist.", infile); + } + + RUN(read_file_stdin(&b, infile)); + j = 0; + for(i = 0; i < MACRO_MIN(1, b->n_lines); i++){ + j += b->l[i]->len - 1; + } + if(j == 0){ + free_in_buffer(b); + *msa = backup; + return OK; + } + + RUN(detect_alignment_format(b, &type)); + if(type == FORMAT_FA){ + RUN(read_fasta(b, &m)); + }else if(type == FORMAT_MSF){ + RUN(read_msf(b, &m)); + }else if(type == FORMAT_CLU){ + RUN(read_clu(b, &m)); + }else{ + free_in_buffer(b); + ERROR_MSG("Could not detect input format in: %s", infile ? infile : "stdin"); + } + m->quiet = quiet; + RUN(detect_alphabet(m)); + RUN(detect_aligned(m)); + RUN(set_sip_nsip(m)); + free_in_buffer(b); + + if(m->numseq < 1){ + ERROR_MSG("No sequences found in %s", infile ? infile : "stdin"); + } + + if(backup != NULL){ + RUN(merge_msa(&backup, m)); + kalign_free_msa(m); + *msa = backup; + }else{ + *msa = m; + } + return OK; +ERROR: + if(m) kalign_free_msa(m); + *msa = backup; + return FAIL; +} + int check_for_sequences(struct msa* msa) { if(!msa){ @@ -255,7 +319,6 @@ int detect_alignment_format(struct in_buffer*b,int* type) //char line[BUFFER_LEN]; int hints[3]; - int line_len; int line_number; int set; int i; @@ -271,18 +334,7 @@ int detect_alignment_format(struct in_buffer*b,int* type) } for(i = 0; i < MACRO_MIN(b->n_lines, 100);i++){ line = b->l[i]->line; - line_len = b->l[i]->len; - - //} - //RUNP(f_ptr = fopen(infile, "r")); - - /* scan through first line header */ - //while ((nread = getline(&line, &b_len, f_ptr)) != -1){ - //while(fgets(line, BUFFER_LEN, f_ptr)){ - //line_len = nread;//strnlen(line, BUFFER_LEN); - //line[line_len-1] = 0; - line_len--; if(line[0] == '>'){ hints[0]++; /* fasta */ } @@ -352,7 +404,7 @@ int read_file_stdin(struct in_buffer** buffer,char* infile) char* line = NULL; char* tmp = NULL; size_t b_len = 0; - size_t nread; + ssize_t nread; int i; //char line[BUFFER_LEN]; int line_len; diff --git a/lib/src/msa_op.c b/lib/src/msa_op.c index b1b8620..aaf4641 100644 --- a/lib/src/msa_op.c +++ b/lib/src/msa_op.c @@ -3,6 +3,7 @@ #include "alphabet.h" #include +#include #include "msa_alloc.h" #define MSA_OP_IMPORT @@ -67,7 +68,7 @@ int msa_seq_cpy(struct msa_seq *d, struct msa_seq *src) d->s[j] = src->s[j]; d->gaps[j] = src->gaps[j]; } - d->gaps[src->alloc_len] = src->gaps[src->alloc_len]; + d->gaps[src->len] = src->gaps[src->len]; d->seq[src->len] = 0; d->len = src->len; d->rank = src->rank; @@ -308,7 +309,7 @@ int set_sip_nsip(struct msa* msa) for (i =0;i < msa->num_profiles;i++){ msa->sip[i] = NULL; msa->nsip[i] = 0; - + msa->plen[i] = 0; } for(i = 0;i < msa->numseq;i++){ @@ -386,6 +387,8 @@ int kalign_msa_to_arr(struct msa* msa, char ***aligned, int *out_aln_len) for(int i = 0 ; i < numseq;i++){ out[i] = NULL; MMALLOC(out[i], sizeof(char) * (aln_len +1)); + memset(out[i], '-', aln_len); + out[i][aln_len] = 0; } /* galloc(&out, numseq,aln_len+1); */ @@ -457,7 +460,11 @@ int kalign_arr_to_msa(char** input_sequences, int* len, int numseq,struct msa** msa->col_confidence = NULL; msa->seq_weights = NULL; msa->run_parallel = 0; +#ifdef USE_THREADPOOL + msa->pool = NULL; +#endif msa->consistency_table = NULL; + msa->poar_consistency = NULL; msa->quiet = 1; MMALLOC(msa->sequences, sizeof(struct msa_seq*) * msa->alloc_numseq); @@ -480,6 +487,7 @@ int kalign_arr_to_msa(char** input_sequences, int* len, int numseq,struct msa** seq->alloc_len = len[i]+1; MMALLOC(seq->name, sizeof(char)* MSA_NAME_LEN); + snprintf(seq->name, MSA_NAME_LEN, "seq%d", i); MMALLOC(seq->seq, sizeof(char) * seq->alloc_len); MMALLOC(seq->s, sizeof(uint8_t) * seq->alloc_len); @@ -545,54 +553,158 @@ static int aln_unknown_warning_message_same_len_no_gaps(void) int finalise_alignment(struct msa* msa) { - ASSERT(msa->aligned == ALN_STATUS_ALIGNED, "Sequences are not aligned"); - struct msa_seq* seq = NULL; - char* linear_seq = NULL; + char** linear_seqs = NULL; int aln_len = 0; + ASSERT(msa->aligned == ALN_STATUS_ALIGNED, "Sequences are not aligned"); - for(int i = 0; i <= msa->sequences[0]->len;i++){ aln_len += msa->sequences[0]->gaps[i]; } aln_len += msa->sequences[0]->len; + /* Two-pass to keep finalisation atomic w.r.t. msa->sequences: + build and validate every linear_seq first; only swap pointers + into msa->sequences[i]->seq once we know every sequence is + consistent. On error the MSA's per-sequence buffers are + untouched and callers can safely retry or fall back. */ + MMALLOC(linear_seqs, sizeof(char*) * msa->numseq); + for(int i = 0; i < msa->numseq; i++) linear_seqs[i] = NULL; + for(int i = 0; i < msa->numseq;i++){ - MMALLOC(linear_seq, sizeof(char)* (aln_len+1)); - seq = msa->sequences[i]; - RUN(make_linear_sequence(seq,linear_seq)); - MFREE(seq->seq); - seq->seq = linear_seq; - /* seq->len = aln_len; */ - linear_seq = NULL; + int seq_aln_len = 0; + struct msa_seq* seq = msa->sequences[i]; + + MMALLOC(linear_seqs[i], sizeof(char) * (aln_len + 1)); + memset(linear_seqs[i], '-', aln_len); + linear_seqs[i][aln_len] = 0; + + RUN(make_linear_sequence(seq, linear_seqs[i], &seq_aln_len)); + if(seq_aln_len != aln_len){ + ERROR_MSG("Alignment length mismatch: seq %d (%s) " + "has length %d, expected %d", + i, seq->name, seq_aln_len, aln_len); + } + } + + /* All sequences validated — commit the swap. */ + for(int i = 0; i < msa->numseq; i++){ + MFREE(msa->sequences[i]->seq); + msa->sequences[i]->seq = linear_seqs[i]; } + MFREE(linear_seqs); msa->alnlen = aln_len; msa->aligned = ALN_STATUS_FINAL; return OK; ERROR: + if(linear_seqs){ + for(int i = 0; i < msa->numseq; i++){ + if(linear_seqs[i]) MFREE(linear_seqs[i]); + } + MFREE(linear_seqs); + } return FAIL; } -int make_linear_sequence(struct msa_seq* seq, char* linear_seq) +int make_linear_sequence(struct msa_seq* seq, char* linear_seq, int* out_len) { int c,j,f; f = 0; for(j = 0;j < seq->len;j++){ - //LOG_MSG("%d %d",j,seq->gaps[j]); for(c = 0;c < seq->gaps[j];c++){ linear_seq[f] = '-'; f++; - } - //LOG_MSG("%d %d %d",j,f,seq->gaps[j]); linear_seq[f] = seq->seq[j]; f++; } for(c = 0;c < seq->gaps[ seq->len];c++){ - //LOG_MSG("%d %d",j,seq->gaps[seq->len]); linear_seq[f] = '-'; f++; } linear_seq[f] = 0; - ///fprintf(stdout,"LINEAR:%s\n",linear_seq); + if(out_len) *out_len = f; + return OK; +} + +int kalign_msa_get_biotype(struct msa *msa) +{ + if(!msa) return ALN_BIOTYPE_UNDEF; + return msa->biotype; +} + +/* ======================================================================== */ +/* Confidence masking */ +/* ======================================================================== */ + +#define KALIGN_MASK_LOWERCASE 0 +#define KALIGN_MASK_REMOVE 1 + +int kalign_mask_by_confidence(struct msa* msa, float threshold, int style) +{ + int i, col; + + ASSERT(msa != NULL, "No MSA"); + + if(threshold <= 0.0f){ + return OK; /* no masking requested */ + } + + if(msa->col_confidence == NULL){ + WARNING_MSG("No confidence scores available (requires ensemble mode). " + "Skipping masking."); + return OK; + } + + ASSERT(msa->aligned == ALN_STATUS_FINAL, "MSA must be finalized"); + ASSERT(msa->alnlen > 0, "Alignment length is 0"); + + for(col = 0; col < msa->alnlen; col++){ + if(msa->col_confidence[col] >= threshold){ + continue; /* column is confident */ + } + /* Mask this column in all sequences */ + for(i = 0; i < msa->numseq; i++){ + char c = msa->sequences[i]->seq[col]; + if(c == '-') continue; /* gaps stay gaps */ + if(style == KALIGN_MASK_REMOVE){ + msa->sequences[i]->seq[col] = '-'; + }else{ + /* KALIGN_MASK_LOWERCASE */ + msa->sequences[i]->seq[col] = (char)tolower((unsigned char)c); + } + } + } + + return OK; +ERROR: + return FAIL; +} + +int kalign_write_confidence(struct msa* msa, const char* path) +{ + FILE* fp = NULL; + int col; + + ASSERT(msa != NULL, "No MSA"); + ASSERT(path != NULL, "No output path"); + + if(msa->col_confidence == NULL){ + WARNING_MSG("No confidence scores to write."); + return OK; + } + + fp = fopen(path, "w"); + if(!fp){ + ERROR_MSG("Cannot open %s for writing", path); + } + + for(col = 0; col < msa->alnlen; col++){ + fprintf(fp, "%.4f\n", msa->col_confidence[col]); + } + + fclose(fp); return OK; +ERROR: + if(fp) fclose(fp); + return FAIL; } diff --git a/lib/src/msa_op.h b/lib/src/msa_op.h index f424882..97bd60e 100644 --- a/lib/src/msa_op.h +++ b/lib/src/msa_op.h @@ -30,7 +30,19 @@ EXTERN int kalign_msa_to_arr(struct msa *msa, char ***aligned, int *out_aln_len) EXTERN int kalign_arr_to_msa(char **input_sequences, int *len, int numseq, struct msa **multiple_aln); EXTERN int finalise_alignment(struct msa* msa); -EXTERN int make_linear_sequence(struct msa_seq *seq, char *linear_seq); +EXTERN int make_linear_sequence(struct msa_seq *seq, char *linear_seq, int *out_len); + +/* Confidence masking styles */ +#define KALIGN_MASK_LOWERCASE 0 +#define KALIGN_MASK_REMOVE 1 + +/* Mask low-confidence alignment columns. + style: KALIGN_MASK_LOWERCASE (residues → lowercase) or KALIGN_MASK_REMOVE (→ gaps). + No-op if threshold <= 0 or col_confidence is NULL (non-ensemble modes). */ +EXTERN int kalign_mask_by_confidence(struct msa* msa, float threshold, int style); + +/* Write per-column confidence scores to a text file (one value per line). */ +EXTERN int kalign_write_confidence(struct msa* msa, const char* path); #undef MSA_OP_IMPORT #undef EXTERN diff --git a/lib/src/msa_struct.h b/lib/src/msa_struct.h index cd2869f..9433555 100644 --- a/lib/src/msa_struct.h +++ b/lib/src/msa_struct.h @@ -3,6 +3,10 @@ #include +#ifdef USE_THREADPOOL +typedef struct threadpool threadpool_t; +#endif + #ifdef MSA_STRUCT_IMPORT #define EXTERN #else @@ -40,6 +44,9 @@ struct msa{ int* nsip; int* plen; uint8_t run_parallel; +#ifdef USE_THREADPOOL + threadpool_t *pool; +#endif int numseq; int num_profiles; int alloc_numseq; @@ -50,6 +57,7 @@ struct msa{ uint8_t biotype; int quiet; void* consistency_table; /* struct consistency_table*, NULL when disabled */ + void* poar_consistency; /* struct poar_consistency_ctx*, NULL when disabled */ }; #undef MSA_STRUCT_IMPORT diff --git a/lib/src/pick_anchor.c b/lib/src/pick_anchor.c index 07e3092..71f29c8 100644 --- a/lib/src/pick_anchor.c +++ b/lib/src/pick_anchor.c @@ -1,84 +1,184 @@ #include "tldevel.h" #include "msa_struct.h" +#include "bpm.h" -#define PICK_ANCHOR_IMPORT -#include "pick_anchor.h" - +#ifdef USE_THREADPOOL +#include "threadpool.h" -struct sort_struct{ - int len; - int id; +struct anchor_dist_ctx { + struct msa_seq** seqs; + float* min_dist; + uint8_t* anchor_s; + int anchor_len; }; -int sort_by_len(const void *a, const void *b); - -int* select_seqs(struct msa* msa, int num_anchor); - -int* pick_anchor(struct msa* msa, int* n) +static void anchor_init_fn(int start, int end, void *arg) { - int* anchors = NULL; - int num_anchor = 0; - - ASSERT(msa != NULL, "No alignment."); - - /* num_anchor = MACRO_MAX(MACRO_MIN(32, msa->numseq), (int) pow(log2((double) msa->numseq), 2.0)); */ - num_anchor = MACRO_MIN(32, msa->numseq); - RUNP(anchors = select_seqs(msa, num_anchor)); - *n = num_anchor; - return anchors; -ERROR: - return NULL; + struct anchor_dist_ctx *c = (struct anchor_dist_ctx *)arg; + for (int i = start; i < end; i++) { + uint8_t *si = c->seqs[i]->s; + int li = c->seqs[i]->len; + if (li > c->anchor_len) + c->min_dist[i] = (float)BPM(si, c->anchor_s, li, c->anchor_len); + else + c->min_dist[i] = (float)BPM(c->anchor_s, si, c->anchor_len, li); + } } -int* select_seqs(struct msa* msa, int num_anchor) +static void anchor_update_fn(int start, int end, void *arg) { - struct sort_struct** seq_sort = NULL; - int* anchors = NULL; - int i,stride; - - MMALLOC(seq_sort, sizeof(struct sort_struct*) * msa->numseq); - for(i = 0; i < msa->numseq;i++){ - seq_sort[i] = NULL; - MMALLOC(seq_sort[i], sizeof(struct sort_struct)); - seq_sort[i]->id = i; - seq_sort[i]->len = msa->sequences[i]->len;// aln->sl[i]; + struct anchor_dist_ctx *c = (struct anchor_dist_ctx *)arg; + for (int i = start; i < end; i++) { + if (c->min_dist[i] < 0.0f) continue; + uint8_t *si = c->seqs[i]->s; + int li = c->seqs[i]->len; + float d; + if (li > c->anchor_len) + d = (float)BPM(si, c->anchor_s, li, c->anchor_len); + else + d = (float)BPM(c->anchor_s, si, c->anchor_len, li); + if (d < c->min_dist[i]) + c->min_dist[i] = d; } +} +#endif - qsort(seq_sort, msa->numseq, sizeof(struct sort_struct*),sort_by_len); - /* for(i = 0; i < msa->numseq;i++){ */ - /* fprintf(stdout,"%d\t%d id: %d \n", seq_sort[i]->id,seq_sort[i]->len,seq_sort[i]->id); */ - /* } */ - +#define PICK_ANCHOR_IMPORT +#include "pick_anchor.h" - //fprintf(stdout,"%d\t seeds\n", num_anchor); +#define DEFAULT_NUM_ANCHORS 32 + +/* Farthest-first anchor selection. + * + * Picks K diverse anchor sequences by greedily selecting the sequence + * most distant (by BPM edit distance) from all already-selected anchors. + * This ensures the anchors span the full diversity of the dataset. + * + * Cost: K * N BPM calls — microseconds for typical inputs. + */ +static int* pick_anchor_farthest_first(struct msa* msa, int K, int* n_out) +{ + int numseq = msa->numseq; + int* anchors = NULL; + float* min_dist = NULL; /* min distance from each seq to any anchor */ + int i, k; + + if(K > numseq) K = numseq; + if(K < 1) K = 1; + + MMALLOC(anchors, sizeof(int) * K); + MMALLOC(min_dist, sizeof(float) * numseq); + + /* Pick first anchor: median-length sequence. + Sort would be overkill — just find the sequence closest + to the mean length. */ + { + float mean_len = 0.0f; + float best_diff = 1e30f; + int best_idx = 0; + for(i = 0; i < numseq; i++){ + mean_len += (float)msa->sequences[i]->len; + } + mean_len /= (float)numseq; + for(i = 0; i < numseq; i++){ + float diff = (float)msa->sequences[i]->len - mean_len; + if(diff < 0) diff = -diff; + if(diff < best_diff){ + best_diff = diff; + best_idx = i; + } + } + anchors[0] = best_idx; + } - MMALLOC(anchors, sizeof(int) * num_anchor); - stride = msa->numseq / num_anchor; -// fprintf(stdout,"%d\tstride\n", stride); - //c = 0; - for(i = 0; i < num_anchor;i++){ - anchors[i] = seq_sort[i*stride]->id; - /* LOG_MSG("Anchor: %d",anchors[i] ); */ + /* Initialize min_dist: BPM distance from each seq to first anchor */ + { + uint8_t* anchor_s = msa->sequences[anchors[0]]->s; + int anchor_len = msa->sequences[anchors[0]]->len; +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct anchor_dist_ctx ctx = { msa->sequences, min_dist, anchor_s, anchor_len }; + tp_parallel_for_chunked(msa->pool, 0, numseq, KALIGN_PFOR_MIN_CHUNK, anchor_init_fn, &ctx); + }else{ +#endif + for(i = 0; i < numseq; i++){ + uint8_t* si = msa->sequences[i]->s; + int li = msa->sequences[i]->len; + if(li > anchor_len){ + min_dist[i] = (float)BPM(si, anchor_s, li, anchor_len); + }else{ + min_dist[i] = (float)BPM(anchor_s, si, anchor_len, li); + } + } +#ifdef USE_THREADPOOL + } +#endif + min_dist[anchors[0]] = -1.0f; /* mark as selected */ } - ASSERT(i == num_anchor,"Cound not select all anchors\tnum_anchor:%d\t numseq:%d",num_anchor,msa->numseq); - for(i = 0; i < msa->numseq;i++){ - MFREE(seq_sort[i]); + /* Farthest-first: pick remaining K-1 anchors */ + for(k = 1; k < K; k++){ + /* Find the sequence with largest min_dist */ + float best_min = -1.0f; + int best_idx = 0; + for(i = 0; i < numseq; i++){ + if(min_dist[i] > best_min){ + best_min = min_dist[i]; + best_idx = i; + } + } + anchors[k] = best_idx; + min_dist[best_idx] = -1.0f; /* mark as selected */ + + /* Update min_dist with new anchor */ + uint8_t* anchor_s = msa->sequences[best_idx]->s; + int anchor_len = msa->sequences[best_idx]->len; +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct anchor_dist_ctx ctx = { msa->sequences, min_dist, anchor_s, anchor_len }; + tp_parallel_for_chunked(msa->pool, 0, numseq, KALIGN_PFOR_MIN_CHUNK, anchor_update_fn, &ctx); + }else{ +#endif + for(i = 0; i < numseq; i++){ + if(min_dist[i] < 0.0f) continue; /* already selected */ + uint8_t* si = msa->sequences[i]->s; + int li = msa->sequences[i]->len; + float d; + if(li > anchor_len){ + d = (float)BPM(si, anchor_s, li, anchor_len); + }else{ + d = (float)BPM(anchor_s, si, anchor_len, li); + } + if(d < min_dist[i]){ + min_dist[i] = d; + } + } +#ifdef USE_THREADPOOL + } +#endif } - MFREE(seq_sort); + + MFREE(min_dist); + *n_out = K; return anchors; ERROR: + if(anchors) MFREE(anchors); + if(min_dist) MFREE(min_dist); return NULL; } -int sort_by_len(const void *a, const void *b) +int* pick_anchor(struct msa* msa, int* n) { - struct sort_struct* const *one = a; - struct sort_struct* const *two = b; + ASSERT(msa != NULL, "No alignment."); + return pick_anchor_farthest_first(msa, DEFAULT_NUM_ANCHORS, n); +ERROR: + return NULL; +} - if((*one)->len > (*two)->len){ - return -1; - }else{ - return 1; - } +int* pick_anchor_n(struct msa* msa, int requested, int* n) +{ + ASSERT(msa != NULL, "No alignment."); + return pick_anchor_farthest_first(msa, requested, n); +ERROR: + return NULL; } diff --git a/lib/src/pick_anchor.h b/lib/src/pick_anchor.h index 5aa4093..938c462 100644 --- a/lib/src/pick_anchor.h +++ b/lib/src/pick_anchor.h @@ -14,6 +14,7 @@ struct msa; EXTERN int* pick_anchor(struct msa* msa, int* n); +EXTERN int* pick_anchor_n(struct msa* msa, int requested, int* n); #undef PICK_ANCHOR_IMPORT diff --git a/lib/src/poar.c b/lib/src/poar.c index c9cbf6a..68d458a 100644 --- a/lib/src/poar.c +++ b/lib/src/poar.c @@ -1,9 +1,14 @@ #include "tldevel.h" #include +#include #include #include +#ifdef USE_THREADPOOL +#include "threadpool/threadpool.h" +#endif + #define POAR_IMPORT #include "poar.h" @@ -168,7 +173,46 @@ void pos_matrix_free(struct pos_matrix* pm) } } -int extract_poars(struct poar_table* table, struct pos_matrix* pm, int aln_idx) +#ifdef USE_THREADPOOL +struct extract_poar_ctx { + struct poar_table* table; + struct pos_matrix* pm; + int numseq; + int alnlen; + int aln_idx; + int error; +}; + +static void extract_poar_chunk(int start, int end, void* arg) +{ + struct extract_poar_ctx* c = (struct extract_poar_ctx*)arg; + int j, col; + for(int i = start; i < end; i++){ + for(j = i + 1; j < c->numseq; j++){ + int pidx = pair_index(i, j, c->numseq); + struct poar_pair* pp = c->table->pairs[pidx]; + for(col = 0; col < c->alnlen; col++){ + int ri = c->pm->col_to_res[i][col]; + int rj = c->pm->col_to_res[j][col]; + if(ri >= 0 && rj >= 0){ + uint32_t key = pack_key(ri, rj); + if(poar_pair_insert(pp, key, c->aln_idx) != OK){ + c->error = 1; + return; + } + } + } + } + } +} +#endif + +int extract_poars(struct poar_table* table, struct pos_matrix* pm, + int aln_idx +#ifdef USE_THREADPOOL + , threadpool_t* pool +#endif + ) { int i, j, col; int numseq = pm->numseq; @@ -176,17 +220,30 @@ int extract_poars(struct poar_table* table, struct pos_matrix* pm, int aln_idx) ASSERT(aln_idx < 32, "Maximum 32 alignments supported in ensemble"); - for(i = 0; i < numseq - 1; i++){ - for(j = i + 1; j < numseq; j++){ - int pidx = pair_index(i, j, numseq); - struct poar_pair* pp = table->pairs[pidx]; - - for(col = 0; col < alnlen; col++){ - int ri = pm->col_to_res[i][col]; - int rj = pm->col_to_res[j][col]; - if(ri >= 0 && rj >= 0){ - uint32_t key = pack_key(ri, rj); - RUN(poar_pair_insert(pp, key, aln_idx)); +#ifdef USE_THREADPOOL + if(pool != NULL && numseq > 16){ + struct extract_poar_ctx ctx = { + table, pm, numseq, alnlen, aln_idx, 0 + }; + tp_parallel_for(pool, 0, numseq - 1, + extract_poar_chunk, &ctx); + if(ctx.error){ + ERROR_MSG("Parallel POAR extraction failed"); + } + }else +#endif + { + for(i = 0; i < numseq - 1; i++){ + for(j = i + 1; j < numseq; j++){ + int pidx = pair_index(i, j, numseq); + struct poar_pair* pp = table->pairs[pidx]; + for(col = 0; col < alnlen; col++){ + int ri = pm->col_to_res[i][col]; + int rj = pm->col_to_res[j][col]; + if(ri >= 0 && rj >= 0){ + uint32_t key = pack_key(ri, rj); + RUN(poar_pair_insert(pp, key, aln_idx)); + } } } } @@ -282,7 +339,15 @@ int poar_table_read(struct poar_table** out_table, const char* path) t->pairs = NULL; t->numseq = (int)numseq; t->n_alignments = (int)n_alignments; - t->n_pairs = (int)(numseq * (numseq - 1) / 2); + + /* Compute pair count in 64-bit; numseq*(numseq-1) overflows uint32 at + numseq >= 65537, and the cast to int wraps negative at numseq >= 65536. */ + uint64_t n_pairs64 = (uint64_t)numseq * ((uint64_t)numseq - 1) / 2; + if(n_pairs64 > (uint64_t)INT_MAX){ + ERROR_MSG("POAR file %s: numseq=%u implies %llu pairs, exceeds INT_MAX", + path, numseq, (unsigned long long)n_pairs64); + } + t->n_pairs = (int)n_pairs64; MMALLOC(t->pairs, sizeof(struct poar_pair*) * t->n_pairs); for(i = 0; i < t->n_pairs; i++){ @@ -297,6 +362,13 @@ int poar_table_read(struct poar_table** out_table, const char* path) ERROR_MSG("Failed to read pair %d entries count", i); } + /* Cap so the cast to int doesn't wrap negative and the per-entry + allocation size doesn't overflow size_t. */ + if(n_entries > (uint32_t)(INT_MAX / sizeof(struct poar_entry))){ + ERROR_MSG("POAR file: pair %d has n_entries=%u, exceeds bounds", + i, n_entries); + } + MMALLOC(pp, sizeof(struct poar_pair)); pp->entries = NULL; pp->n_entries = (int)n_entries; diff --git a/lib/src/poar.h b/lib/src/poar.h index 7921814..9d652ea 100644 --- a/lib/src/poar.h +++ b/lib/src/poar.h @@ -47,7 +47,14 @@ EXTERN void poar_table_free(struct poar_table* table); EXTERN int pos_matrix_from_msa(struct pos_matrix** pm, char** seqs, int numseq, int alnlen); EXTERN void pos_matrix_free(struct pos_matrix* pm); -EXTERN int extract_poars(struct poar_table* table, struct pos_matrix* pm, int aln_idx); +#ifdef USE_THREADPOOL +typedef struct threadpool threadpool_t; +EXTERN int extract_poars(struct poar_table* table, struct pos_matrix* pm, + int aln_idx, threadpool_t* pool); +#else +EXTERN int extract_poars(struct poar_table* table, struct pos_matrix* pm, + int aln_idx); +#endif EXTERN int poar_table_write(struct poar_table* table, const char* path); EXTERN int poar_table_read(struct poar_table** table, const char* path); diff --git a/lib/src/sequence_distance.c b/lib/src/sequence_distance.c index 1e71eb6..7fb4143 100644 --- a/lib/src/sequence_distance.c +++ b/lib/src/sequence_distance.c @@ -7,6 +7,10 @@ #include "msa_struct.h" +#ifdef USE_THREADPOOL +#include "threadpool.h" +#endif + #define SEQUENCE_DISTANCE_IMPORT #include "sequence_distance.h" @@ -17,6 +21,32 @@ /* #include "misc.h" */ #include "bpm.h" +#ifdef USE_THREADPOOL +struct dist_row_ctx { + struct msa_seq** seqs; + float** dm; + int* samples; + int num_samples; +}; + +static void dist_row_fn(int row_start, int row_end, void *arg) +{ + struct dist_row_ctx *c = (struct dist_row_ctx *)arg; + for (int r = row_start; r < row_end; r++) { + uint8_t *s1 = c->seqs[r]->s; + int l1 = c->seqs[r]->len; + for (int k = 0; k < c->num_samples; k++) { + uint8_t *s2 = c->seqs[c->samples[k]]->s; + int l2 = c->seqs[c->samples[k]]->len; + c->dm[r][k] = calc_distance(s1, s2, l1, l2); + int avg = (l1 + l2) / 2; + float add = MACRO_MIN(10000.0, avg) / 10000.0; + c->dm[r][k] += add; + } + } +} +#endif + #define NODESIZE 16 /* small hash implementation */ @@ -55,17 +85,11 @@ float** d_estimation(struct msa* msa, int* samples, int num_samples,int pair) RUN(galloc(&dm,num_samples,num_samples)); for(i = 0; i < num_samples;i++){ - seq_a = msa->sequences[samples[i]]->s;// aln->s[samples[i]]; - len_a = msa->sequences[samples[i]]->len;//aln->sl[samples[i]]; + seq_a = msa->sequences[samples[i]]->s; + len_a = msa->sequences[samples[i]]->len; for(j = 0;j < num_samples;j++){ - //fprintf(stdout, "Working on %d %d\n", i,j); - - seq_b = msa->sequences[samples[j]]->s; //aln->s[ samples[j]]; - len_b = msa->sequences[samples[j]]->len;//aln->sl[selection[j]]; - /*dm[i][j] = MACRO_MIN(len_a, len_b) - MACRO_MIN( - bpm_256(seq_a, seq_b, len_a, len_b), - bpm_256(seq_b, seq_a, len_b, len_a) - );*/ + seq_b = msa->sequences[samples[j]]->s; + len_b = msa->sequences[samples[j]]->len; dist = calc_distance(seq_a, seq_b, len_a, len_b); /* give shorter sequences a preference */ int s = (len_a + len_b) / 2; @@ -104,8 +128,17 @@ float** d_estimation(struct msa* msa, int* samples, int num_samples,int pair) } struct msa_seq** s = msa->sequences; + +#ifdef USE_THREADPOOL + if(msa->pool && numseq >= KALIGN_DIST_MIN_SEQS){ + struct dist_row_ctx ctx = { s, dm, samples, num_samples }; + tp_parallel_for_chunked(msa->pool, 0, numseq, KALIGN_PFOR_MIN_CHUNK, dist_row_fn, &ctx); + }else{ +#endif +#if !defined(USE_THREADPOOL) #ifdef HAVE_OPENMP #pragma omp parallel for shared(dm, s) private(i, j) collapse(2) schedule(static) +#endif #endif for(i = 0; i < numseq;i++){ for(j = 0;j < num_samples;j++){ @@ -118,16 +151,14 @@ float** d_estimation(struct msa* msa, int* samples, int num_samples,int pair) s2 = s[samples[j]]->s; l2 = s[samples[j]]->len; dm[i][j] = calc_distance(s1,s2,l1,l2); - int s = (l1 + l2) / 2; - float add = MACRO_MIN(10000.0, s) / 10000.0; + int sv = (l1 + l2) / 2; + float add = MACRO_MIN(10000.0, sv) / 10000.0; dm[i][j] += add; - /* fprintf(stdout,"%f ",dm[i][j]); */ - /* dm[i][j] += (float)MACRO_MIN(l1, l2) / (float)MACRO_MAX(l1, l2); */ - /* dm[i][j] = dm[i][j] / (float) MACRO_MIN(l1, l2); */ - //dm[i][j] = dist; } - /* fprintf(stdout,"\n"); */ } +#ifdef USE_THREADPOOL + } +#endif /* fprintf(stdout,"\n"); */ diff --git a/lib/src/task.c b/lib/src/task.c index ce12fe5..5596193 100644 --- a/lib/src/task.c +++ b/lib/src/task.c @@ -183,7 +183,13 @@ int alloc_tasks(struct aln_tasks** tasks,int numseq) for(i = 0; i < t->n_alloc_tasks;i++){ t->list[i] = NULL; MMALLOC(t->list[i], sizeof(struct task)); + t->list[i]->score = 0.0F; t->list[i]->confidence = 0.0F; + t->list[i]->a = 0; + t->list[i]->b = 0; + t->list[i]->c = 0; + t->list[i]->p = 0; + t->list[i]->n = 0; } *tasks = t; diff --git a/lib/src/threadpool/CMakeLists.txt b/lib/src/threadpool/CMakeLists.txt new file mode 100644 index 0000000..2039972 --- /dev/null +++ b/lib/src/threadpool/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.18) + +# Support standalone build +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(threadpool LANGUAGES C) + set(CMAKE_C_STANDARD 11) + set(CMAKE_C_STANDARD_REQUIRED ON) + enable_testing() + + # Optional: find OpenMP for benchmark comparison + find_package(OpenMP) +endif() + +find_package(Threads REQUIRED) + +# ── Thread pool library ─────────────────────────────────────────── +add_library(threadpool STATIC threadpool.c) +target_include_directories(threadpool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(threadpool PUBLIC Threads::Threads) +set_target_properties(threadpool PROPERTIES C_STANDARD 11) + +# ── Tests ───────────────────────────────────────────────────────── +add_executable(test_threadpool test_threadpool.c) +target_link_libraries(test_threadpool PRIVATE threadpool) +set_target_properties(test_threadpool PROPERTIES C_STANDARD 11) +add_test(NAME threadpool_tests COMMAND test_threadpool) + +add_executable(stress_threadpool stress_threadpool.c) +target_link_libraries(stress_threadpool PRIVATE threadpool) +set_target_properties(stress_threadpool PROPERTIES C_STANDARD 11) +add_test(NAME threadpool_stress COMMAND stress_threadpool) + +# ── Benchmarks ──────────────────────────────────────────────────── +add_executable(bench_threadpool bench_threadpool.c) +target_link_libraries(bench_threadpool PRIVATE threadpool) +set_target_properties(bench_threadpool PROPERTIES C_STANDARD 11) + +if(OpenMP_C_FOUND) + target_link_libraries(bench_threadpool PRIVATE OpenMP::OpenMP_C) + target_compile_definitions(bench_threadpool PRIVATE HAVE_OPENMP) +endif() diff --git a/lib/src/threadpool/bench_threadpool.c b/lib/src/threadpool/bench_threadpool.c new file mode 100644 index 0000000..e9292ea --- /dev/null +++ b/lib/src/threadpool/bench_threadpool.c @@ -0,0 +1,538 @@ +/* + * bench_threadpool.c — Benchmarks for the three kalign parallelism patterns. + * + * 1. Distance matrix (parallel for) + * 2. Recursive tree (task spawning) + * 3. Fork-join 2 tasks (Hirschberg-like) + * + * Each benchmark compares serial, threadpool (1..N threads), + * and optionally OpenMP. + */ + +#include "threadpool.h" +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_OPENMP +#include +#endif + +/* ── Timing ───────────────────────────────────────────────────── */ + +static double now(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; +} + +static int cmp_double(const void *a, const void *b) +{ + double da = *(const double *)a; + double db = *(const double *)b; + return (da > db) - (da < db); +} + +#define NREPS 7 + +static double median(double *arr, int n) +{ + qsort(arr, (size_t)n, sizeof(double), cmp_double); + return arr[n / 2]; +} + +static void fmt_time(double secs, char *buf, int len) +{ + if (secs < 1e-3) + snprintf(buf, (size_t)len, "%7.1f us", secs * 1e6); + else if (secs < 1.0) + snprintf(buf, (size_t)len, "%7.2f ms", secs * 1e3); + else + snprintf(buf, (size_t)len, "%7.3f s ", secs); +} + +/* ── Workload helpers ─────────────────────────────────────────── */ + +static uint8_t **create_sequences(int n, int len) +{ + uint8_t **seqs = (uint8_t **)malloc((size_t)n * sizeof(uint8_t *)); + for (int i = 0; i < n; i++) { + seqs[i] = (uint8_t *)malloc((size_t)len); + unsigned x = (unsigned)i * 2654435761u; + for (int j = 0; j < len; j++) { + x = x * 1103515245u + 12345u; + seqs[i][j] = (uint8_t)((x >> 16) % 20); + } + } + return seqs; +} + +static void free_sequences(uint8_t **seqs, int n) +{ + for (int i = 0; i < n; i++) free(seqs[i]); + free(seqs); +} + +/* ══════════════════════════════════════════════════════════════════ + * 1. DISTANCE MATRIX (parallel for) + * ══════════════════════════════════════════════════════════════════ */ + +struct dm_ctx { + float **dm; + uint8_t **seqs; + int seq_len; + int num_anchors; +}; + +static void dm_chunk(int start, int end, void *arg) +{ + struct dm_ctx *ctx = (struct dm_ctx *)arg; + for (int i = start; i < end; i++) { + for (int j = 0; j < ctx->num_anchors; j++) { + int diff = 0; + for (int k = 0; k < ctx->seq_len; k++) + diff += (ctx->seqs[i][k] != ctx->seqs[j][k]); + ctx->dm[i][j] = (float)diff / (float)ctx->seq_len; + } + } +} + +static double bench_dm_serial(uint8_t **seqs, float **dm, + int N, int M, int L) +{ + struct dm_ctx ctx = { dm, seqs, L, M }; + double t0 = now(); + dm_chunk(0, N, &ctx); + return now() - t0; +} + +static double bench_dm_tp(uint8_t **seqs, float **dm, + int N, int M, int L, int nthreads) +{ + struct dm_ctx ctx = { dm, seqs, L, M }; + threadpool_t *pool = tp_create(nthreads); + double t0 = now(); + tp_parallel_for(pool, 0, N, dm_chunk, &ctx); + double elapsed = now() - t0; + tp_destroy(pool); + return elapsed; +} + +#ifdef HAVE_OPENMP +static double bench_dm_omp(uint8_t **seqs, float **dm, + int N, int M, int L) +{ + double t0 = now(); + int i, j, k; + #pragma omp parallel for shared(dm, seqs) private(i, j, k) schedule(static) + for (i = 0; i < N; i++) { + for (j = 0; j < M; j++) { + int diff = 0; + for (k = 0; k < L; k++) + diff += (seqs[i][k] != seqs[j][k]); + dm[i][j] = (float)diff / (float)L; + } + } + return now() - t0; +} +#endif + +/* ══════════════════════════════════════════════════════════════════ + * 2. RECURSIVE TREE (task spawning) + * ══════════════════════════════════════════════════════════════════ */ + +struct tree_node { + struct tree_node *left; + struct tree_node *right; + int leaf_id; + int depth; + float result; +}; + +static struct tree_node *build_tree(int depth, int *next_id) +{ + struct tree_node *n = (struct tree_node *)calloc(1, sizeof(*n)); + n->depth = depth; + if (depth == 0) { + n->leaf_id = (*next_id)++; + return n; + } + n->leaf_id = -1; + n->left = build_tree(depth - 1, next_id); + n->right = build_tree(depth - 1, next_id); + return n; +} + +static void free_tree(struct tree_node *n) +{ + if (!n) return; + free_tree(n->left); + free_tree(n->right); + free(n); +} + +#define LEAF_ITERS 500 + +static float leaf_work(int id) +{ + float sum = 0.0f; + unsigned x = (unsigned)id * 2654435761u; + for (int i = 0; i < LEAF_ITERS; i++) { + x = x * 1103515245u + 12345u; + sum += (float)(x >> 16) / 65536.0f; + } + return sum; +} + +/* Serial */ +static void tree_serial(struct tree_node *n) +{ + if (n->leaf_id >= 0) { n->result = leaf_work(n->leaf_id); return; } + tree_serial(n->left); + tree_serial(n->right); + n->result = n->left->result + n->right->result; +} + +/* Threadpool */ +struct tree_tp_arg { + threadpool_t *pool; + struct tree_node *node; +}; + +/* Serial cutoff: avoid task creation overhead for tiny subtrees. + * Not needed for correctness (V2 Chase-Lev bounds stack to O(depth)), + * but worthwhile for performance — each task group costs a malloc. */ +#define TREE_TASK_CUTOFF 4 + +static void tree_tp_task(void *arg) +{ + struct tree_tp_arg *ta = (struct tree_tp_arg *)arg; + struct tree_node *n = ta->node; + + if (n->leaf_id >= 0) { n->result = leaf_work(n->leaf_id); return; } + + if (n->depth <= TREE_TASK_CUTOFF) { + tree_serial(n); + return; + } + + struct tree_tp_arg left = { ta->pool, n->left }; + struct tree_tp_arg right = { ta->pool, n->right }; + + tp_group_t *g = tp_group_create(ta->pool); + tp_group_submit(g, tree_tp_task, &left); + tp_group_submit(g, tree_tp_task, &right); + tp_group_wait(g); + tp_group_destroy(g); + + n->result = n->left->result + n->right->result; +} + +#ifdef HAVE_OPENMP +static void tree_omp_task(struct tree_node *n) +{ + if (n->leaf_id >= 0) { n->result = leaf_work(n->leaf_id); return; } + + #pragma omp task shared(n) + tree_omp_task(n->left); + #pragma omp task shared(n) + tree_omp_task(n->right); + #pragma omp taskwait + + n->result = n->left->result + n->right->result; +} +#endif + +/* ══════════════════════════════════════════════════════════════════ + * 3. FORK-JOIN (Hirschberg-style: 2 independent tasks) + * ══════════════════════════════════════════════════════════════════ */ + +struct fj_sum_arg { + const float *arr; + int start; + int end; + double result; +}; + +static void fj_sum_task(void *arg) +{ + struct fj_sum_arg *a = (struct fj_sum_arg *)arg; + double s = 0.0; + for (int i = a->start; i < a->end; i++) + s += (double)a->arr[i]; + a->result = s; +} + +static double bench_fj_serial(const float *arr, int N) +{ + double s = 0.0; + double t0 = now(); + for (int i = 0; i < N; i++) s += (double)arr[i]; + double elapsed = now() - t0; + (void)s; + return elapsed; +} + +static double bench_fj_tp(const float *arr, int N, int nthreads) +{ + threadpool_t *pool = tp_create(nthreads); + int mid = N / 2; + struct fj_sum_arg left = { arr, 0, mid, 0.0 }; + struct fj_sum_arg right = { arr, mid, N, 0.0 }; + + double t0 = now(); + tp_group_t *g = tp_group_create(pool); + tp_group_submit(g, fj_sum_task, &left); + tp_group_submit(g, fj_sum_task, &right); + tp_group_wait(g); + tp_group_destroy(g); + double elapsed = now() - t0; + + (void)(left.result + right.result); + tp_destroy(pool); + return elapsed; +} + +/* ══════════════════════════════════════════════════════════════════ + * 4. OVERHEAD MEASUREMENT + * ══════════════════════════════════════════════════════════════════ */ + +static void noop_task(void *arg) { (void)arg; } + +static double bench_overhead(int nthreads) +{ + threadpool_t *pool = tp_create(nthreads); + int N = 10000; + + double t0 = now(); + for (int i = 0; i < N; i++) { + tp_group_t *g = tp_group_create(pool); + tp_group_submit(g, noop_task, NULL); + tp_group_wait(g); + tp_group_destroy(g); + } + double elapsed = now() - t0; + + tp_destroy(pool); + return elapsed / N; +} + +/* ══════════════════════════════════════════════════════════════════ + * MAIN + * ══════════════════════════════════════════════════════════════════ */ + +int main(void) +{ + int ncpus = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus <= 0) ncpus = 4; + + /* Thread counts to test */ + int tc[] = { 1, 2, 4, 8, 16 }; + int ntc = 0; + for (int i = 0; i < 5; i++) { + if (tc[i] <= ncpus * 2) ntc++; + } + + printf("threadpool benchmark\n"); + printf("====================\n"); + printf("CPUs: %d\n\n", ncpus); + + /* ── Overhead ─────────────────────────────────────────────── */ + { + char buf[32]; + double per_task = bench_overhead(ncpus); + fmt_time(per_task, buf, sizeof(buf)); + printf("Per-task overhead (submit+wait+destroy): %s\n\n", buf); + } + + /* ── Distance Matrix ──────────────────────────────────────── */ + { + struct { int N, M, L; } sizes[] = { + { 500, 50, 200 }, + { 2000, 100, 200 }, + { 5000, 100, 200 }, + }; + int nsizes = 3; + + printf("--- Distance Matrix (parallel for) ---\n"); + printf(" %-14s %10s", "Size", "serial"); + for (int t = 0; t < ntc; t++) + printf(" tp(%2d) ", tc[t]); +#ifdef HAVE_OPENMP + printf(" omp "); +#endif + printf("\n"); + + for (int s = 0; s < nsizes; s++) { + int N = sizes[s].N, M = sizes[s].M, L = sizes[s].L; + + uint8_t **seqs = create_sequences(N, L); + float **dm = (float **)malloc((size_t)N * sizeof(float *)); + for (int i = 0; i < N; i++) + dm[i] = (float *)calloc((size_t)M, sizeof(float)); + + char label[32]; + snprintf(label, sizeof(label), "%dx%d", N, M); + printf(" %-14s", label); + + /* Serial */ + double times[NREPS]; + for (int r = 0; r < NREPS; r++) + times[r] = bench_dm_serial(seqs, dm, N, M, L); + double serial_t = median(times, NREPS); + char buf[32]; + fmt_time(serial_t, buf, sizeof(buf)); + printf(" %s", buf); + + /* Threadpool */ + for (int t = 0; t < ntc; t++) { + for (int r = 0; r < NREPS; r++) + times[r] = bench_dm_tp(seqs, dm, N, M, L, tc[t]); + double tp_t = median(times, NREPS); + fmt_time(tp_t, buf, sizeof(buf)); + printf(" %s", buf); + } + +#ifdef HAVE_OPENMP + for (int r = 0; r < NREPS; r++) + times[r] = bench_dm_omp(seqs, dm, N, M, L); + double omp_t = median(times, NREPS); + fmt_time(omp_t, buf, sizeof(buf)); + printf(" %s", buf); +#endif + printf("\n"); + + for (int i = 0; i < N; i++) free(dm[i]); + free(dm); + free_sequences(seqs, N); + } + printf("\n"); + } + + /* ── Recursive Tree ───────────────────────────────────────── */ + { + int depths[] = { 10, 14, 17 }; + int ndepths = 3; + + printf("--- Recursive Tree (task spawning) ---\n"); + printf(" %-14s %10s", "Depth/Leaves", "serial"); + for (int t = 0; t < ntc; t++) + printf(" tp(%2d) ", tc[t]); +#ifdef HAVE_OPENMP + printf(" omp "); +#endif + printf("\n"); + + for (int d = 0; d < ndepths; d++) { + int depth = depths[d]; + int nleaves = 1 << depth; + + char label[32]; + snprintf(label, sizeof(label), "d=%d (%d)", depth, nleaves); + printf(" %-14s", label); + + double times[NREPS]; + char buf[32]; + + /* Serial */ + for (int r = 0; r < NREPS; r++) { + int id = 0; + struct tree_node *root = build_tree(depth, &id); + double t0 = now(); + tree_serial(root); + times[r] = now() - t0; + free_tree(root); + } + fmt_time(median(times, NREPS), buf, sizeof(buf)); + printf(" %s", buf); + + /* Threadpool */ + for (int t = 0; t < ntc; t++) { + for (int r = 0; r < NREPS; r++) { + int id = 0; + struct tree_node *root = build_tree(depth, &id); + threadpool_t *pool = tp_create(tc[t]); + struct tree_tp_arg targ = { pool, root }; + double t0 = now(); + tree_tp_task(&targ); + times[r] = now() - t0; + tp_destroy(pool); + free_tree(root); + } + fmt_time(median(times, NREPS), buf, sizeof(buf)); + printf(" %s", buf); + } + +#ifdef HAVE_OPENMP + for (int r = 0; r < NREPS; r++) { + int id = 0; + struct tree_node *root = build_tree(depth, &id); + double t0 = now(); + #pragma omp parallel + #pragma omp single nowait + tree_omp_task(root); + times[r] = now() - t0; + free_tree(root); + } + fmt_time(median(times, NREPS), buf, sizeof(buf)); + printf(" %s", buf); +#endif + printf("\n"); + } + printf("\n"); + } + + /* ── Fork-Join ────────────────────────────────────────────── */ + { + int fsizes[] = { 10000, 100000, 1000000, 10000000 }; + int nfsizes = 4; + + printf("--- Fork-Join (2 tasks, array sum) ---\n"); + printf(" %-14s %10s", "N", "serial"); + for (int t = 0; t < ntc; t++) + printf(" tp(%2d) ", tc[t]); + printf("\n"); + + for (int s = 0; s < nfsizes; s++) { + int N = fsizes[s]; + float *arr = (float *)malloc((size_t)N * sizeof(float)); + unsigned x = 12345; + for (int i = 0; i < N; i++) { + x = x * 1103515245u + 12345u; + arr[i] = (float)(x >> 16) / 65536.0f; + } + + char label[32]; + if (N >= 1000000) + snprintf(label, sizeof(label), "%dM", N / 1000000); + else + snprintf(label, sizeof(label), "%dK", N / 1000); + printf(" %-14s", label); + + double times[NREPS]; + char buf[32]; + + for (int r = 0; r < NREPS; r++) + times[r] = bench_fj_serial(arr, N); + fmt_time(median(times, NREPS), buf, sizeof(buf)); + printf(" %s", buf); + + for (int t = 0; t < ntc; t++) { + for (int r = 0; r < NREPS; r++) + times[r] = bench_fj_tp(arr, N, tc[t]); + fmt_time(median(times, NREPS), buf, sizeof(buf)); + printf(" %s", buf); + } + printf("\n"); + + free(arr); + } + printf("\n"); + } + + return 0; +} diff --git a/lib/src/threadpool/stress_threadpool.c b/lib/src/threadpool/stress_threadpool.c new file mode 100644 index 0000000..2966d5c --- /dev/null +++ b/lib/src/threadpool/stress_threadpool.c @@ -0,0 +1,625 @@ +/* + * stress_threadpool.c — Stress tests for production readiness. + * + * Covers scenarios the unit tests don't: + * 1. High contention (many submitters, few workers) + * 2. Rapid pool create/destroy cycles + * 3. Deque overflow → ext queue fallback + * 4. Oversubscription (more workers than cores) + * 5. Sustained mixed-pattern load + * 6. Shutdown during active work (tp_request_shutdown) + * 7. Correctness under sustained parallel-for pressure + */ + +#include "threadpool.h" +#include +#include +#include +#include +#include +#include + +/* ── TSan detection ────────────────────────────────────────────── + * TSan can't trace happens-before through the threadpool's fence- + * based synchronization to user data. We add explicit annotations + * for tree-structured tests where the main thread writes data that + * workers later read through a chain of task submissions. */ +#if defined(__SANITIZE_THREAD__) +#define STRESS_TSAN 1 +#elif defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define STRESS_TSAN 1 +#endif +#endif + +#ifdef STRESS_TSAN +void __tsan_acquire(void *addr); +void __tsan_release(void *addr); +#define TSAN_RELEASE(addr) __tsan_release(addr) +#define TSAN_ACQUIRE(addr) __tsan_acquire(addr) +#else +#define TSAN_RELEASE(addr) ((void)0) +#define TSAN_ACQUIRE(addr) ((void)0) +#endif + +/* ── Test harness ─────────────────────────────────────────────── */ + +static int g_pass, g_fail, g_total; + +#define CHECK(cond) do { \ + if (!(cond)) { \ + fprintf(stderr, " FAIL: %s (line %d)\n", \ + #cond, __LINE__); \ + return -1; \ + } \ +} while (0) + +#define RUN(name) do { \ + printf(" %-50s ", #name); \ + fflush(stdout); \ + g_total++; \ + if (test_##name() == 0) { printf("PASS\n"); g_pass++; } \ + else { printf("FAIL\n"); g_fail++; } \ +} while (0) + +static double now_sec(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; +} + +/* ── 1. High contention: many tasks from main thread ─────────── */ +/* All tasks go through ext queue since the main thread is not a + * worker. Stresses ext queue locking under contention. */ + +static void atomic_inc(void *arg) +{ + atomic_int *c = (atomic_int *)arg; + atomic_fetch_add(c, 1); +} + +static int test_high_contention_ext_queue(void) +{ + int N = 100000; + threadpool_t *pool = tp_create(8); + CHECK(pool != NULL); + + atomic_int counter; + atomic_init(&counter, 0); + + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + for (int i = 0; i < N; i++) { + int rc = tp_group_submit(g, atomic_inc, &counter); + CHECK(rc == 0); + } + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(atomic_load(&counter) == N); + tp_destroy(pool); + return 0; +} + +/* ── 2. Rapid pool create/destroy cycles ─────────────────────── */ +/* Catches resource leaks (threads, mutexes, memory). */ + +static int test_rapid_create_destroy(void) +{ + for (int i = 0; i < 200; i++) { + threadpool_t *pool = tp_create(4); + CHECK(pool != NULL); + + /* Do a tiny bit of work each time to exercise the full path. */ + atomic_int counter; + atomic_init(&counter, 0); + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + tp_group_submit(g, atomic_inc, &counter); + tp_group_wait(g); + tp_group_destroy(g); + CHECK(atomic_load(&counter) == 1); + + tp_destroy(pool); + } + return 0; +} + +/* ── 3. Deque overflow → ext queue fallback ──────────────────── */ +/* A worker task submits > DEQUE_DEFAULT_CAP (4096) children. + * Once the deque fills, remaining tasks must go to the ext queue. + * All tasks must still execute correctly. */ + +struct burst_ctx { + threadpool_t *pool; + atomic_int *counter; + int burst_size; +}; + +static void burst_parent(void *arg) +{ + struct burst_ctx *ctx = (struct burst_ctx *)arg; + tp_group_t *g = tp_group_create(ctx->pool); + if (!g) return; + + for (int i = 0; i < ctx->burst_size; i++) { + tp_group_submit(g, atomic_inc, ctx->counter); + } + tp_group_wait(g); + tp_group_destroy(g); +} + +static int test_deque_overflow_fallback(void) +{ + threadpool_t *pool = tp_create(2); + CHECK(pool != NULL); + + atomic_int counter; + atomic_init(&counter, 0); + + /* Submit 8000 tasks from a worker context — exceeds deque cap of 4096. */ + struct burst_ctx ctx = { pool, &counter, 8000 }; + + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + tp_group_submit(g, burst_parent, &ctx); + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(atomic_load(&counter) == 8000); + tp_destroy(pool); + return 0; +} + +/* ── 4. Oversubscription ─────────────────────────────────────── */ +/* More workers than CPU cores. Must not deadlock or starve. */ + +static void fill_val(int start, int end, void *arg) +{ + int *a = (int *)arg; + for (int i = start; i < end; i++) + a[i] = i + 1; +} + +static int test_oversubscription(void) +{ + int ncpus = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus <= 0) ncpus = 4; + int nthreads = ncpus * 4; /* 4x oversubscription */ + + threadpool_t *pool = tp_create(nthreads); + CHECK(pool != NULL); + CHECK(tp_get_nthreads(pool) == nthreads); + + /* Parallel for with many chunks. */ + int N = 10000; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + tp_parallel_for(pool, 0, N, fill_val, arr); + + for (int i = 0; i < N; i++) + CHECK(arr[i] == i + 1); + + free(arr); + tp_destroy(pool); + return 0; +} + +/* ── 5. Sustained mixed-pattern load ─────────────────────────── */ +/* Run parallel-for + recursive tasks + fork-join concurrently + * on the same pool over many iterations. */ + +struct tree_node_s { + struct tree_node_s *left; + struct tree_node_s *right; + int leaf_id; + atomic_int result; +}; + +static struct tree_node_s *build_small_tree(int depth, int *next_id) +{ + struct tree_node_s *n = (struct tree_node_s *)calloc(1, sizeof(*n)); + if (!n) return NULL; + atomic_init(&n->result, 0); + if (depth == 0) { + n->leaf_id = (*next_id)++; + return n; + } + n->leaf_id = -1; + n->left = build_small_tree(depth - 1, next_id); + n->right = build_small_tree(depth - 1, next_id); + return n; +} + +static void free_small_tree(struct tree_node_s *n) +{ + if (!n) return; + free_small_tree(n->left); + free_small_tree(n->right); + free(n); +} + +struct stree_arg { + threadpool_t *pool; + struct tree_node_s *node; + struct tree_node_s *root; /* sync point for TSan */ +}; + +static void stree_task(void *arg) +{ + struct stree_arg *ta = (struct stree_arg *)arg; + struct tree_node_s *n = ta->node; + TSAN_ACQUIRE(ta->root); /* pairs with TSAN_RELEASE after tree build */ + + if (n->leaf_id >= 0) { + atomic_store(&n->result, n->leaf_id + 1); + return; + } + + struct stree_arg left = { ta->pool, n->left, ta->root }; + struct stree_arg right = { ta->pool, n->right, ta->root }; + + tp_group_t *g = tp_group_create(ta->pool); + if (!g) return; + tp_group_submit(g, stree_task, &left); + tp_group_submit(g, stree_task, &right); + tp_group_wait(g); + tp_group_destroy(g); + + atomic_store(&n->result, + atomic_load(&n->left->result) + + atomic_load(&n->right->result)); +} + +static int serial_tree_sum(struct tree_node_s *n) +{ + if (n->leaf_id >= 0) return n->leaf_id + 1; + return serial_tree_sum(n->left) + serial_tree_sum(n->right); +} + +static void pfor_fill(int start, int end, void *arg) +{ + int *a = (int *)arg; + for (int i = start; i < end; i++) + a[i] = i * i; +} + +static int test_sustained_mixed_patterns(void) +{ + threadpool_t *pool = tp_create(8); + CHECK(pool != NULL); + + int N = 5000; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + for (int iter = 0; iter < 50; iter++) { + /* Parallel for */ + memset(arr, 0, (size_t)N * sizeof(int)); + tp_parallel_for(pool, 0, N, pfor_fill, arr); + for (int i = 0; i < N; i++) + CHECK(arr[i] == i * i); + + /* Recursive tree */ + int id = 0; + struct tree_node_s *root = build_small_tree(8, &id); /* 256 leaves */ + CHECK(root != NULL); + int expected = serial_tree_sum(root); + + TSAN_RELEASE(root); /* tree is fully built; pairs with ACQUIRE in stree_task */ + struct stree_arg targ = { pool, root, root }; + tp_group_t *tg = tp_group_create(pool); + CHECK(tg != NULL); + tp_group_submit(tg, stree_task, &targ); + tp_group_wait(tg); + tp_group_destroy(tg); + CHECK(atomic_load(&root->result) == expected); + free_small_tree(root); + + /* Fork-join */ + atomic_int counter; + atomic_init(&counter, 0); + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + for (int i = 0; i < 100; i++) + tp_group_submit(g, atomic_inc, &counter); + tp_group_wait(g); + tp_group_destroy(g); + CHECK(atomic_load(&counter) == 100); + } + + free(arr); + tp_destroy(pool); + return 0; +} + +/* ── 6. tp_request_shutdown during active work ───────────────── */ +/* Verify that tp_group_wait returns after shutdown is requested, + * and that tp_destroy completes without hanging. */ + +static void slow_task(void *arg) +{ + atomic_int *counter = (atomic_int *)arg; + /* Simulate some work. */ + volatile int sink = 0; + for (int i = 0; i < 10000; i++) + sink += i; + (void)sink; + atomic_fetch_add(counter, 1); +} + +static int test_shutdown_during_work(void) +{ + threadpool_t *pool = tp_create(4); + CHECK(pool != NULL); + + atomic_int counter; + atomic_init(&counter, 0); + + /* Submit a batch of tasks. */ + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + for (int i = 0; i < 1000; i++) + tp_group_submit(g, slow_task, &counter); + + /* Request shutdown immediately — some tasks may not finish. */ + tp_request_shutdown(pool); + + /* tp_group_wait should return (possibly early). */ + tp_group_wait(g); + tp_group_destroy(g); + + /* tp_destroy should not hang. */ + tp_destroy(pool); + + /* At least some tasks should have run. */ + int completed = atomic_load(&counter); + CHECK(completed > 0); + + return 0; +} + +/* ── 7. Parallel-for correctness under sustained pressure ────── */ +/* Many iterations of parallel-for on the same pool, verifying + * every element every time. */ + +static void fill_with_check(int start, int end, void *arg) +{ + int *a = (int *)arg; + for (int i = start; i < end; i++) + a[i] = i * 3 + 7; +} + +static int test_parallel_for_sustained(void) +{ + threadpool_t *pool = tp_create(8); + CHECK(pool != NULL); + + int N = 50000; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + for (int iter = 0; iter < 100; iter++) { + memset(arr, 0, (size_t)N * sizeof(int)); + tp_parallel_for(pool, 0, N, fill_with_check, arr); + for (int i = 0; i < N; i++) + CHECK(arr[i] == i * 3 + 7); + } + + free(arr); + tp_destroy(pool); + return 0; +} + +/* ── 8. Single-element ranges and edge cases ─────────────────── */ + +static void count_calls(int start, int end, void *arg) +{ + atomic_int *c = (atomic_int *)arg; + for (int i = start; i < end; i++) + atomic_fetch_add(c, 1); +} + +static int test_edge_cases(void) +{ + threadpool_t *pool = tp_create(4); + CHECK(pool != NULL); + + /* Single element */ + atomic_int counter; + atomic_init(&counter, 0); + tp_parallel_for(pool, 0, 1, count_calls, &counter); + CHECK(atomic_load(&counter) == 1); + + /* Two elements */ + atomic_init(&counter, 0); + tp_parallel_for(pool, 0, 2, count_calls, &counter); + CHECK(atomic_load(&counter) == 2); + + /* Range smaller than thread count */ + atomic_init(&counter, 0); + tp_parallel_for(pool, 0, 3, count_calls, &counter); + CHECK(atomic_load(&counter) == 3); + + /* Range equal to thread count */ + atomic_init(&counter, 0); + tp_parallel_for(pool, 0, 4, count_calls, &counter); + CHECK(atomic_load(&counter) == 4); + + /* Negative/empty ranges */ + atomic_init(&counter, 0); + tp_parallel_for(pool, 5, 5, count_calls, &counter); + CHECK(atomic_load(&counter) == 0); + tp_parallel_for(pool, 10, 3, count_calls, &counter); + CHECK(atomic_load(&counter) == 0); + + /* NULL pool is UB, so we don't test it. */ + + tp_destroy(pool); + return 0; +} + +/* ── 9. Many sequential groups (lifecycle churn) ─────────────── */ + +static int test_group_lifecycle_churn(void) +{ + threadpool_t *pool = tp_create(4); + CHECK(pool != NULL); + + for (int i = 0; i < 10000; i++) { + atomic_int counter; + atomic_init(&counter, 0); + + tp_group_t *g = tp_group_create(pool); + CHECK(g != NULL); + tp_group_submit(g, atomic_inc, &counter); + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(atomic_load(&counter) == 1); + } + + tp_destroy(pool); + return 0; +} + +/* ── 10. Deeply nested recursive tasks ───────────────────────── */ +/* Depth 20 = 1M leaves. LIFO pop bounds stack to O(20) per + * thread. Would overflow with a naive FIFO approach. */ + +struct deep_node { + struct deep_node *left; + struct deep_node *right; + int leaf_id; + int result; +}; + +static struct deep_node *build_deep_tree(int depth, int *next_id) +{ + struct deep_node *n = (struct deep_node *)calloc(1, sizeof(*n)); + if (!n) return NULL; + if (depth == 0) { + n->leaf_id = (*next_id)++; + return n; + } + n->leaf_id = -1; + n->left = build_deep_tree(depth - 1, next_id); + n->right = build_deep_tree(depth - 1, next_id); + return n; +} + +static void free_deep_tree(struct deep_node *n) +{ + if (!n) return; + free_deep_tree(n->left); + free_deep_tree(n->right); + free(n); +} + +struct deep_arg { + threadpool_t *pool; + struct deep_node *node; + int cutoff; +}; + +static int serial_deep_sum(struct deep_node *n) +{ + if (n->leaf_id >= 0) return 1; + return serial_deep_sum(n->left) + serial_deep_sum(n->right); +} + +static void deep_serial_task(struct deep_node *n) +{ + if (n->leaf_id >= 0) { n->result = 1; return; } + deep_serial_task(n->left); + deep_serial_task(n->right); + n->result = n->left->result + n->right->result; +} + +static void deep_task(void *arg) +{ + struct deep_arg *da = (struct deep_arg *)arg; + struct deep_node *n = da->node; + + if (n->leaf_id >= 0) { n->result = 1; return; } + + if (da->cutoff <= 0) { + /* Below cutoff: run serially to avoid task-creation overhead. */ + deep_serial_task(n); + return; + } + + struct deep_arg left = { da->pool, n->left, da->cutoff - 1 }; + struct deep_arg right = { da->pool, n->right, da->cutoff - 1 }; + + tp_group_t *g = tp_group_create(da->pool); + if (!g) { deep_serial_task(n); return; } + tp_group_submit(g, deep_task, &left); + tp_group_submit(g, deep_task, &right); + tp_group_wait(g); + tp_group_destroy(g); + + n->result = n->left->result + n->right->result; +} + +static int test_deep_tree_stress(void) +{ +#ifdef STRESS_TSAN + /* TSan inflates stack frames ~5x; reduce depth to avoid overflow + * during recursive tp_group_wait → execute_task → deep_task chains. */ + int depth = 14; + int cutoff = 5; +#else + int depth = 20; /* 1,048,576 leaves */ + int cutoff = 10; +#endif + int id = 0; + struct deep_node *root = build_deep_tree(depth, &id); + CHECK(root != NULL); + CHECK(id == (1 << depth)); + + int expected = serial_deep_sum(root); + CHECK(expected == (1 << depth)); + + threadpool_t *pool = tp_create(8); + CHECK(pool != NULL); + + struct deep_arg da = { pool, root, cutoff }; + deep_task(&da); + + CHECK(root->result == expected); + + tp_destroy(pool); + free_deep_tree(root); + return 0; +} + +/* ── main ─────────────────────────────────────────────────────── */ + +int main(void) +{ + printf("threadpool stress tests\n"); + printf("=======================\n\n"); + + double t0 = now_sec(); + + RUN(high_contention_ext_queue); + RUN(rapid_create_destroy); + RUN(deque_overflow_fallback); + RUN(oversubscription); + RUN(sustained_mixed_patterns); + RUN(shutdown_during_work); + RUN(parallel_for_sustained); + RUN(edge_cases); + RUN(group_lifecycle_churn); + RUN(deep_tree_stress); + + double elapsed = now_sec() - t0; + printf("\n%d/%d passed (%.1fs)", g_pass, g_total, elapsed); + if (g_fail > 0) printf(", %d FAILED", g_fail); + printf("\n"); + + return g_fail > 0 ? 1 : 0; +} diff --git a/lib/src/threadpool/test_threadpool.c b/lib/src/threadpool/test_threadpool.c new file mode 100644 index 0000000..08a7319 --- /dev/null +++ b/lib/src/threadpool/test_threadpool.c @@ -0,0 +1,579 @@ +/* + * test_threadpool.c — Tests for all three parallelism patterns. + * + * 1. Parallel for (distance-matrix style) + * 2. Fork-join (Hirschberg forward/backward) + * 3. Recursive tasks (guide-tree traversal) + */ + +#include "threadpool.h" +#include +#include +#include +#include + +/* ── Test harness ─────────────────────────────────────────────── */ + +static int g_pass, g_fail, g_total; + +#define CHECK(cond) do { \ + if (!(cond)) { \ + fprintf(stderr, " FAIL: %s (line %d)\n", \ + #cond, __LINE__); \ + return -1; \ + } \ +} while (0) + +#define RUN(name) do { \ + printf(" %-45s ", #name); \ + fflush(stdout); \ + g_total++; \ + if (test_##name() == 0) { printf("PASS\n"); g_pass++; } \ + else { printf("FAIL\n"); g_fail++; } \ +} while (0) + +/* ── 1. create / destroy ─────────────────────────────────────── */ + +static int test_create_destroy(void) +{ + threadpool_t *p = tp_create(2); + CHECK(p != NULL); + CHECK(tp_get_nthreads(p) == 2); + tp_destroy(p); + return 0; +} + +static int test_create_auto(void) +{ + threadpool_t *p = tp_create(0); + CHECK(p != NULL); + CHECK(tp_get_nthreads(p) >= 1); + tp_destroy(p); + return 0; +} + +/* ── 2. parallel for ─────────────────────────────────────────── */ + +static void fill_squares(int start, int end, void *arg) +{ + int *arr = (int *)arg; + for (int i = start; i < end; i++) + arr[i] = i * i; +} + +static int test_parallel_for_correctness(void) +{ + int N = 10000; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + threadpool_t *pool = tp_create(4); + CHECK(pool != NULL); + + tp_parallel_for(pool, 0, N, fill_squares, arr); + + for (int i = 0; i < N; i++) + CHECK(arr[i] == i * i); + + tp_destroy(pool); + free(arr); + return 0; +} + +static int test_parallel_for_single_thread(void) +{ + int N = 5000; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + threadpool_t *pool = tp_create(1); + tp_parallel_for(pool, 0, N, fill_squares, arr); + + for (int i = 0; i < N; i++) + CHECK(arr[i] == i * i); + + tp_destroy(pool); + free(arr); + return 0; +} + +static int test_parallel_for_empty_range(void) +{ + threadpool_t *pool = tp_create(2); + /* Should be a no-op, not crash. */ + tp_parallel_for(pool, 5, 5, fill_squares, NULL); + tp_parallel_for(pool, 10, 3, fill_squares, NULL); + tp_destroy(pool); + return 0; +} + +/* Distance-matrix style: outer loop parallel, inner loop serial. */ + +struct dm_ctx { + float **dm; + uint8_t **seqs; + int seq_len; + int num_anchors; +}; + +static void dm_chunk(int start, int end, void *arg) +{ + struct dm_ctx *ctx = (struct dm_ctx *)arg; + for (int i = start; i < end; i++) { + for (int j = 0; j < ctx->num_anchors; j++) { + int diff = 0; + for (int k = 0; k < ctx->seq_len; k++) + diff += (ctx->seqs[i][k] != ctx->seqs[j][k]); + ctx->dm[i][j] = (float)diff / (float)ctx->seq_len; + } + } +} + +static int test_parallel_for_distance_matrix(void) +{ + int N = 200, M = 20, L = 50; + + /* Allocate sequences */ + uint8_t **seqs = (uint8_t **)malloc((size_t)N * sizeof(uint8_t *)); + CHECK(seqs != NULL); + for (int i = 0; i < N; i++) { + seqs[i] = (uint8_t *)malloc((size_t)L); + for (int j = 0; j < L; j++) + seqs[i][j] = (uint8_t)((i * 7 + j * 13) % 20); + } + + /* Serial reference */ + float **ref = (float **)malloc((size_t)N * sizeof(float *)); + for (int i = 0; i < N; i++) { + ref[i] = (float *)calloc((size_t)M, sizeof(float)); + for (int j = 0; j < M; j++) { + int diff = 0; + for (int k = 0; k < L; k++) + diff += (seqs[i][k] != seqs[j][k]); + ref[i][j] = (float)diff / (float)L; + } + } + + /* Parallel computation */ + float **dm = (float **)malloc((size_t)N * sizeof(float *)); + for (int i = 0; i < N; i++) + dm[i] = (float *)calloc((size_t)M, sizeof(float)); + + struct dm_ctx ctx = { dm, seqs, L, M }; + threadpool_t *pool = tp_create(4); + tp_parallel_for(pool, 0, N, dm_chunk, &ctx); + + for (int i = 0; i < N; i++) + for (int j = 0; j < M; j++) + CHECK(dm[i][j] == ref[i][j]); + + tp_destroy(pool); + for (int i = 0; i < N; i++) { free(seqs[i]); free(ref[i]); free(dm[i]); } + free(seqs); free(ref); free(dm); + return 0; +} + +/* ── 3. fork-join ─────────────────────────────────────────────── */ + +struct fj_arg { + int *out; + int val; +}; + +static void fj_write(void *arg) +{ + struct fj_arg *a = (struct fj_arg *)arg; + *a->out = a->val; +} + +static int test_fork_join_two(void) +{ + threadpool_t *pool = tp_create(2); + int a = 0, b = 0; + struct fj_arg aa = { &a, 42 }; + struct fj_arg bb = { &b, 99 }; + + tp_group_t *g = tp_group_create(pool); + tp_group_submit(g, fj_write, &aa); + tp_group_submit(g, fj_write, &bb); + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(a == 42); + CHECK(b == 99); + tp_destroy(pool); + return 0; +} + +static int test_fork_join_four(void) +{ + threadpool_t *pool = tp_create(4); + int results[4] = {0}; + struct fj_arg args[4]; + for (int i = 0; i < 4; i++) { + args[i].out = &results[i]; + args[i].val = (i + 1) * 10; + } + + tp_group_t *g = tp_group_create(pool); + for (int i = 0; i < 4; i++) + tp_group_submit(g, fj_write, &args[i]); + tp_group_wait(g); + tp_group_destroy(g); + + for (int i = 0; i < 4; i++) + CHECK(results[i] == (i + 1) * 10); + + tp_destroy(pool); + return 0; +} + +/* ── 4. recursive tasks (parallel fibonacci) ──────────────────── */ + +struct fib_arg { + threadpool_t *pool; + int n; + int result; +}; + +static void fib_task(void *arg) +{ + struct fib_arg *f = (struct fib_arg *)arg; + if (f->n <= 1) { + f->result = f->n; + return; + } + struct fib_arg left = { .pool = f->pool, .n = f->n - 1, .result = 0 }; + struct fib_arg right = { .pool = f->pool, .n = f->n - 2, .result = 0 }; + + tp_group_t *g = tp_group_create(f->pool); + tp_group_submit(g, fib_task, &left); + tp_group_submit(g, fib_task, &right); + tp_group_wait(g); + tp_group_destroy(g); + + f->result = left.result + right.result; +} + +static int serial_fib(int n) +{ + if (n <= 1) return n; + return serial_fib(n - 1) + serial_fib(n - 2); +} + +static int test_recursive_fib(void) +{ + threadpool_t *pool = tp_create(4); + + for (int n = 0; n <= 15; n++) { + struct fib_arg f = { .pool = pool, .n = n, .result = -1 }; + fib_task(&f); + CHECK(f.result == serial_fib(n)); + } + + tp_destroy(pool); + return 0; +} + +/* ── 5. recursive tree (simulates guide-tree traversal) ───────── */ + +struct tree_node { + struct tree_node *left; + struct tree_node *right; + int leaf_id; /* >= 0 for leaves, -1 for internal */ + float result; +}; + +static struct tree_node *build_tree(int depth, int *next_id) +{ + struct tree_node *n = (struct tree_node *)calloc(1, sizeof(*n)); + if (depth == 0) { + n->leaf_id = (*next_id)++; + return n; + } + n->leaf_id = -1; + n->left = build_tree(depth - 1, next_id); + n->right = build_tree(depth - 1, next_id); + return n; +} + +static void free_tree(struct tree_node *n) +{ + if (!n) return; + free_tree(n->left); + free_tree(n->right); + free(n); +} + +struct tree_task_arg { + threadpool_t *pool; + struct tree_node *node; +}; + +static float leaf_work(int id) +{ + float sum = 0.0f; + unsigned x = (unsigned)id * 2654435761u; + for (int i = 0; i < 100; i++) { + x = x * 1103515245u + 12345u; + sum += (float)(x >> 16) / 65536.0f; + } + return sum; +} + +static void tree_task(void *arg) +{ + struct tree_task_arg *ta = (struct tree_task_arg *)arg; + struct tree_node *n = ta->node; + + if (n->leaf_id >= 0) { + n->result = leaf_work(n->leaf_id); + return; + } + + struct tree_task_arg left_arg = { ta->pool, n->left }; + struct tree_task_arg right_arg = { ta->pool, n->right }; + + tp_group_t *g = tp_group_create(ta->pool); + tp_group_submit(g, tree_task, &left_arg); + tp_group_submit(g, tree_task, &right_arg); + tp_group_wait(g); + tp_group_destroy(g); + + n->result = n->left->result + n->right->result; +} + +static float serial_tree(struct tree_node *n) +{ + if (n->leaf_id >= 0) + return leaf_work(n->leaf_id); + return serial_tree(n->left) + serial_tree(n->right); +} + +static int test_recursive_tree(void) +{ + int id = 0; + struct tree_node *root = build_tree(10, &id); /* 1024 leaves */ + + float serial_result = serial_tree(root); + + /* Reset results */ + /* (serial_tree wrote into the same nodes — rebuild) */ + free_tree(root); + id = 0; + root = build_tree(10, &id); + + threadpool_t *pool = tp_create(4); + struct tree_task_arg targ = { pool, root }; + tree_task(&targ); + + /* Compare within float tolerance */ + float diff = root->result - serial_result; + if (diff < 0) diff = -diff; + CHECK(diff < 0.001f); + + tp_destroy(pool); + free_tree(root); + return 0; +} + +/* ── 6. many small tasks (stress test) ────────────────────────── */ + +static void increment(void *arg) +{ + atomic_int *counter = (atomic_int *)arg; + atomic_fetch_add(counter, 1); +} + +static int test_many_tasks(void) +{ + int N = 10000; + threadpool_t *pool = tp_create(4); + atomic_int counter; + atomic_init(&counter, 0); + + tp_group_t *g = tp_group_create(pool); + for (int i = 0; i < N; i++) + tp_group_submit(g, increment, &counter); + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(atomic_load(&counter) == N); + tp_destroy(pool); + return 0; +} + +/* ── 7. nested groups (non-recursive) ─────────────────────────── */ + +struct nested_arg { + threadpool_t *pool; + atomic_int *counter; +}; + +static void inner_task(void *arg) +{ + atomic_int *c = (atomic_int *)arg; + atomic_fetch_add(c, 1); +} + +static void outer_task(void *arg) +{ + struct nested_arg *na = (struct nested_arg *)arg; + tp_group_t *g = tp_group_create(na->pool); + for (int i = 0; i < 5; i++) + tp_group_submit(g, inner_task, na->counter); + tp_group_wait(g); + tp_group_destroy(g); +} + +static int test_nested_groups(void) +{ + threadpool_t *pool = tp_create(4); + atomic_int counter; + atomic_init(&counter, 0); + + struct nested_arg na = { pool, &counter }; + + tp_group_t *g = tp_group_create(pool); + for (int i = 0; i < 10; i++) + tp_group_submit(g, outer_task, &na); + tp_group_wait(g); + tp_group_destroy(g); + + /* 10 outer * 5 inner = 50 */ + CHECK(atomic_load(&counter) == 50); + tp_destroy(pool); + return 0; +} + +/* ── 8. group wait with no tasks ──────────────────────────────── */ + +static int test_empty_group_wait(void) +{ + threadpool_t *pool = tp_create(2); + tp_group_t *g = tp_group_create(pool); + tp_group_wait(g); /* should return immediately */ + tp_group_destroy(g); + tp_destroy(pool); + return 0; +} + +/* ── 9. large parallel for ────────────────────────────────────── */ + +static int test_large_parallel_for(void) +{ + /* N must be ≤ 46340 to avoid signed int overflow in i*i + * (sqrt(INT_MAX) ≈ 46340). The compiler exploits overflow UB. */ + int N = 46340; + int *arr = (int *)calloc((size_t)N, sizeof(int)); + CHECK(arr != NULL); + + threadpool_t *pool = tp_create(8); + tp_parallel_for(pool, 0, N, fill_squares, arr); + + for (int i = 0; i < N; i++) + CHECK(arr[i] == i * i); + + tp_destroy(pool); + free(arr); + return 0; +} + +/* ── 10. deep recursive tree (V2 stack-safety proof) ──────────── */ + +/* Depth 16 = 65536 leaves. V1 (global FIFO queue) would need + * O(65536) stack frames in the worst case, overflowing 8 MB. + * V2 (Chase-Lev LIFO) needs O(16) frames per thread. */ +static int test_deep_recursive_tree(void) +{ + int id = 0; + struct tree_node *root = build_tree(16, &id); /* 65536 leaves */ + + /* Serial reference */ + float serial_result = serial_tree(root); + free_tree(root); + id = 0; + root = build_tree(16, &id); + + threadpool_t *pool = tp_create(4); + struct tree_task_arg targ = { pool, root }; + tree_task(&targ); + + float diff = root->result - serial_result; + if (diff < 0) diff = -diff; + CHECK(diff < 0.01f); + + tp_destroy(pool); + free_tree(root); + return 0; +} + +/* ── 11. deep recursive fibonacci ─────────────────────────────── */ + +static int test_deep_recursive_fib(void) +{ + threadpool_t *pool = tp_create(4); + + /* fib(20) = 6765 — creates ~13K groups, ~26K tasks. */ + struct fib_arg f = { .pool = pool, .n = 20, .result = -1 }; + fib_task(&f); + CHECK(f.result == 6765); + + tp_destroy(pool); + return 0; +} + +/* ── 12. multiple sequential groups ───────────────────────────── */ + +static int test_sequential_groups(void) +{ + threadpool_t *pool = tp_create(4); + + for (int round = 0; round < 20; round++) { + atomic_int counter; + atomic_init(&counter, 0); + + tp_group_t *g = tp_group_create(pool); + for (int i = 0; i < 100; i++) + tp_group_submit(g, increment, &counter); + tp_group_wait(g); + tp_group_destroy(g); + + CHECK(atomic_load(&counter) == 100); + } + + tp_destroy(pool); + return 0; +} + +/* ── main ─────────────────────────────────────────────────────── */ + +int main(void) +{ + printf("threadpool tests\n"); + printf("================\n\n"); + + RUN(create_destroy); + RUN(create_auto); + RUN(parallel_for_correctness); + RUN(parallel_for_single_thread); + RUN(parallel_for_empty_range); + RUN(parallel_for_distance_matrix); + RUN(fork_join_two); + RUN(fork_join_four); + RUN(recursive_fib); + RUN(recursive_tree); + RUN(many_tasks); + RUN(nested_groups); + RUN(empty_group_wait); + RUN(large_parallel_for); + RUN(deep_recursive_tree); + RUN(deep_recursive_fib); + RUN(sequential_groups); + + printf("\n%d/%d passed", g_pass, g_total); + if (g_fail > 0) printf(", %d FAILED", g_fail); + printf("\n"); + + return g_fail > 0 ? 1 : 0; +} diff --git a/lib/src/threadpool/threadpool.c b/lib/src/threadpool/threadpool.c new file mode 100644 index 0000000..b66b55d --- /dev/null +++ b/lib/src/threadpool/threadpool.c @@ -0,0 +1,724 @@ +/* + * threadpool.c — Chase-Lev work-stealing thread pool. + * + * See threadpool.h for the public API and usage examples. + * + * Internal architecture: + * + * Queues: + * - Per-worker Chase-Lev deques (lock-free, LIFO pop, FIFO steal). + * - Global "external" queue (mutex-protected) for non-worker submissions. + * + * Work-finding priority (per worker): + * 1. Own deque (LIFO) — preserves DFS order, bounds stack depth. + * 2. External queue — picks up tasks submitted from non-workers. + * 3. Steal from peer — random victim, FIFO steals shallowest work. + * + * Sleeping: + * Event-count protocol (wake_gen + condvar). Workers spin briefly, + * then park. Submitters bump wake_gen and signal if anyone sleeps. + * + * Group recycling: + * Per-worker free lists for tp_group_t objects. Groups are 16 bytes + * and follow strict LIFO create/destroy ordering, so thread-local + * recycling eliminates virtually all malloc/free after warmup. + * + * Stack depth: + * LIFO pop means a recursive tree traversal nests O(tree_depth) wait + * frames per thread, not O(tree_size). For a balanced tree of 1M + * leaves (depth ~20), each frame is ~500 bytes → ~10 KB total. + * + * Ref: Chase & Lev, "Dynamic Circular Work-Stealing Deque", SPAA 2005. + */ + +#include "threadpool.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ── TSan annotations for Chase-Lev deque ──────────────────────── + * TSan can't track happens-before through atomic_thread_fence, + * so we annotate the deque push/pop/steal with explicit acquire/ + * release on the buffer slot. Zero cost when TSan is off. */ +#if defined(__SANITIZE_THREAD__) +#define TP_TSAN 1 +#elif defined(__has_feature) +#if __has_feature(thread_sanitizer) +#define TP_TSAN 1 +#endif +#endif + +#ifdef TP_TSAN +void __tsan_acquire(void *addr); +void __tsan_release(void *addr); +#define TSAN_RELEASE(addr) __tsan_release(addr) +#define TSAN_ACQUIRE(addr) __tsan_acquire(addr) +#else +#define TSAN_RELEASE(addr) ((void)0) +#define TSAN_ACQUIRE(addr) ((void)0) +#endif + +/* Abort on pthread errors that indicate programming bugs (EINVAL, + * EDEADLK, etc.). These never fail in correct code, but catching + * them immediately beats silent corruption. */ +#define PTHREAD_CHECK(call) do { \ + int _rc = (call); \ + if (_rc != 0) { \ + fprintf(stderr, "threadpool: %s failed (%d)\n", #call, _rc); \ + abort(); \ + } \ +} while (0) + +/* ── Internal task ────────────────────────────────────────────── */ + +typedef struct { + void (*fn)(void *); + void *arg; + atomic_int *pending; /* &group->pending, or NULL */ +} tp_task_t; + +/* ══════════════════════════════════════════════════════════════════ + * Chase-Lev work-stealing deque + * + * Owner pushes/pops at bottom (LIFO). Thieves steal from top (FIFO). + * Lock-free: owner ops use fences, steal uses CAS on top. + * Fixed capacity (power of 2). + * ══════════════════════════════════════════════════════════════════ */ + +#define DEQUE_DEFAULT_CAP 4096 /* per worker; holds O(tree_depth) tasks */ + +typedef struct { + tp_task_t *buf; + long cap; /* power of 2 */ + atomic_long bottom; /* modified by owner */ + char _pad[64]; /* keep bottom and top on separate cache lines */ + atomic_long top; /* CAS'd by thieves */ +} ws_deque_t; + +static int deque_init(ws_deque_t *dq, long cap) +{ + dq->buf = (tp_task_t *)calloc((size_t)cap, sizeof(tp_task_t)); + if (!dq->buf) return -1; + dq->cap = cap; + atomic_store_explicit(&dq->bottom, 0, memory_order_relaxed); + atomic_store_explicit(&dq->top, 0, memory_order_relaxed); + return 0; +} + +static void deque_destroy(ws_deque_t *dq) +{ + free(dq->buf); +} + +/* Owner push. Returns 0 on success, -1 if full. */ +static int deque_push(ws_deque_t *dq, tp_task_t task) +{ + long b = atomic_load_explicit(&dq->bottom, memory_order_relaxed); + long t = atomic_load_explicit(&dq->top, memory_order_acquire); + if (b - t >= dq->cap) + return -1; /* full */ + long slot = b & (dq->cap - 1); + dq->buf[slot] = task; + TSAN_RELEASE(&dq->buf[slot]); + atomic_thread_fence(memory_order_release); /* task visible before bottom bumps */ + atomic_store_explicit(&dq->bottom, b + 1, memory_order_relaxed); + return 0; +} + +/* Owner pop (LIFO). Returns 0 on success, -1 if empty. */ +static int deque_pop(ws_deque_t *dq, tp_task_t *out) +{ + long b = atomic_load_explicit(&dq->bottom, memory_order_relaxed) - 1; + atomic_store_explicit(&dq->bottom, b, memory_order_relaxed); + atomic_thread_fence(memory_order_seq_cst); + long t = atomic_load_explicit(&dq->top, memory_order_relaxed); + + if (t <= b) { + long slot = b & (dq->cap - 1); + *out = dq->buf[slot]; + TSAN_ACQUIRE(&dq->buf[slot]); + if (t == b) { + /* Last element — race with a stealer. */ + if (!atomic_compare_exchange_strong_explicit( + &dq->top, &t, t + 1, + memory_order_seq_cst, memory_order_relaxed)) { + /* Stealer won. */ + atomic_store_explicit(&dq->bottom, b + 1, memory_order_relaxed); + return -1; + } + atomic_store_explicit(&dq->bottom, b + 1, memory_order_relaxed); + } + return 0; + } + /* Was already empty. */ + atomic_store_explicit(&dq->bottom, b + 1, memory_order_relaxed); + return -1; +} + +/* Thief steal (FIFO). Returns 0 on success, -1 if empty or contention. */ +static int deque_steal(ws_deque_t *dq, tp_task_t *out) +{ + long t = atomic_load_explicit(&dq->top, memory_order_acquire); + atomic_thread_fence(memory_order_seq_cst); + long b = atomic_load_explicit(&dq->bottom, memory_order_acquire); + + if (t < b) { + long slot = t & (dq->cap - 1); + *out = dq->buf[slot]; + if (!atomic_compare_exchange_strong_explicit( + &dq->top, &t, t + 1, + memory_order_seq_cst, memory_order_relaxed)) + return -1; /* another stealer won */ + TSAN_ACQUIRE(&dq->buf[slot]); + return 0; + } + return -1; /* empty */ +} + +/* ══════════════════════════════════════════════════════════════════ + * External queue — mutex-protected, for non-worker submissions + * ══════════════════════════════════════════════════════════════════ */ + +typedef struct { + tp_task_t *buf; + int cap; + int head; + int tail; + int count; + pthread_mutex_t lock; +} ext_queue_t; + +static int ext_init(ext_queue_t *q, int cap) +{ + q->buf = (tp_task_t *)calloc((size_t)cap, sizeof(tp_task_t)); + if (!q->buf) return -1; + q->cap = cap; + q->head = q->tail = q->count = 0; + if (pthread_mutex_init(&q->lock, NULL) != 0) { + free(q->buf); + q->buf = NULL; + return -1; + } + return 0; +} + +static void ext_destroy(ext_queue_t *q) +{ + free(q->buf); + PTHREAD_CHECK(pthread_mutex_destroy(&q->lock)); +} + +/* Caller must hold q->lock. */ +static int ext_grow_locked(ext_queue_t *q) +{ + if (q->cap > INT_MAX / 2) return -1; /* overflow guard */ + int new_cap = q->cap * 2; + tp_task_t *nb = (tp_task_t *)calloc((size_t)new_cap, sizeof(tp_task_t)); + if (!nb) return -1; + for (int i = 0; i < q->count; i++) + nb[i] = q->buf[(q->head + i) % q->cap]; + free(q->buf); + q->buf = nb; + q->head = 0; + q->tail = q->count; + q->cap = new_cap; + return 0; +} + +static int ext_push(ext_queue_t *q, tp_task_t task) +{ + PTHREAD_CHECK(pthread_mutex_lock(&q->lock)); + if (q->count == q->cap && ext_grow_locked(q) != 0) { + PTHREAD_CHECK(pthread_mutex_unlock(&q->lock)); + return -1; + } + q->buf[q->tail] = task; + q->tail = (q->tail + 1) % q->cap; + q->count++; + PTHREAD_CHECK(pthread_mutex_unlock(&q->lock)); + return 0; +} + +static int ext_try_pop(ext_queue_t *q, tp_task_t *out) +{ + PTHREAD_CHECK(pthread_mutex_lock(&q->lock)); + if (q->count == 0) { + PTHREAD_CHECK(pthread_mutex_unlock(&q->lock)); + return -1; + } + *out = q->buf[q->head]; + q->head = (q->head + 1) % q->cap; + q->count--; + PTHREAD_CHECK(pthread_mutex_unlock(&q->lock)); + return 0; +} + +/* ══════════════════════════════════════════════════════════════════ + * Pool, worker, group structures + * ══════════════════════════════════════════════════════════════════ */ + +#define MAX_FREE_GROUPS 64 + +typedef struct worker { + ws_deque_t deque; + struct threadpool *pool; + pthread_t thread; + int id; + unsigned rng; /* xorshift state for random steal */ + struct tp_group *free_groups; /* per-worker group free list */ + int n_free; +} worker_t; + +struct threadpool { + worker_t *workers; + int nworkers; + ext_queue_t ext; + + atomic_int wake_gen; /* event count for wakeup */ + atomic_int n_sleeping; + pthread_mutex_t wake_lock; + pthread_cond_t wake_cond; + + atomic_int shutdown; +}; + +struct tp_group { + union { + threadpool_t *pool; /* valid when active */ + struct tp_group *next; /* valid when on free list */ + }; + atomic_int pending; +}; + +/* ── Thread-local: current worker (NULL on non-worker threads) ── */ +static _Thread_local worker_t *tl_worker = NULL; + +/* ── RNG for steal-target selection ───────────────────────────── */ + +static unsigned xorshift32(unsigned *s) +{ + unsigned x = *s; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + return *s = x; +} + +/* ── Task execution ───────────────────────────────────────────── */ + +static void execute_task(tp_task_t *t) +{ + t->fn(t->arg); + if (t->pending) + atomic_fetch_sub(t->pending, 1); +} + +/* ── Worker wakeup ────────────────────────────────────────────── */ + +static void notify_workers(threadpool_t *pool) +{ + atomic_fetch_add_explicit(&pool->wake_gen, 1, memory_order_release); + if (atomic_load_explicit(&pool->n_sleeping, memory_order_relaxed) > 0) { + PTHREAD_CHECK(pthread_mutex_lock(&pool->wake_lock)); + PTHREAD_CHECK(pthread_cond_signal(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_unlock(&pool->wake_lock)); + } +} + +/* ── Work-finding protocols ───────────────────────────────────── */ + +/* Called by worker threads: own deque (LIFO) → ext queue → steal. */ +static int try_find_work(worker_t *w, tp_task_t *out) +{ + /* 1. Own deque — LIFO preserves DFS order, bounds stack depth. */ + if (deque_pop(&w->deque, out) == 0) return 0; + + /* 2. External queue — picks up tasks from non-worker submitters. */ + if (ext_try_pop(&w->pool->ext, out) == 0) return 0; + + /* 3. Steal from a random peer — FIFO takes shallowest work. */ + int n = w->pool->nworkers; + if (n > 1) { + int start = (int)(xorshift32(&w->rng) % (unsigned)n); + for (int i = 0; i < n; i++) { + int v = (start + i) % n; + if (v == w->id) continue; + if (deque_steal(&w->pool->workers[v].deque, out) == 0) + return 0; + } + } + return -1; +} + +/* Called by non-worker threads (main thread in tp_group_wait). */ +static int try_find_work_ext(threadpool_t *pool, tp_task_t *out, + unsigned *rng) +{ + if (ext_try_pop(&pool->ext, out) == 0) return 0; + + int n = pool->nworkers; + int start = (int)(xorshift32(rng) % (unsigned)n); + for (int i = 0; i < n; i++) { + int v = (start + i) % n; + if (deque_steal(&pool->workers[v].deque, out) == 0) + return 0; + } + return -1; +} + +/* ── Worker thread ────────────────────────────────────────────── */ + +static void *worker_main(void *arg) +{ + worker_t *w = (worker_t *)arg; + tl_worker = w; + threadpool_t *pool = w->pool; + tp_task_t task; + unsigned spins = 0; + + for (;;) { + if (try_find_work(w, &task) == 0) { + execute_task(&task); + spins = 0; + continue; + } + + if (atomic_load(&pool->shutdown)) + break; + + if (++spins < 64) + continue; /* brief spin before parking */ + + /* Park until new work arrives (event-count protocol). */ + int gen = atomic_load_explicit(&pool->wake_gen, + memory_order_acquire); + atomic_fetch_add(&pool->n_sleeping, 1); + PTHREAD_CHECK(pthread_mutex_lock(&pool->wake_lock)); + + while (atomic_load_explicit(&pool->wake_gen, + memory_order_acquire) == gen + && !atomic_load(&pool->shutdown)) + PTHREAD_CHECK(pthread_cond_wait(&pool->wake_cond, + &pool->wake_lock)); + + PTHREAD_CHECK(pthread_mutex_unlock(&pool->wake_lock)); + atomic_fetch_sub(&pool->n_sleeping, 1); + spins = 0; + } + return NULL; +} + +/* ══════════════════════════════════════════════════════════════════ + * Public API: pool lifecycle + * ══════════════════════════════════════════════════════════════════ */ + +threadpool_t *tp_create(int nthreads) +{ + if (nthreads <= 0) { + nthreads = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (nthreads <= 0) nthreads = 4; + } + + threadpool_t *pool = (threadpool_t *)calloc(1, sizeof(*pool)); + if (!pool) return NULL; + + pool->nworkers = nthreads; + atomic_store_explicit(&pool->shutdown, 0, memory_order_relaxed); + atomic_store_explicit(&pool->wake_gen, 0, memory_order_relaxed); + atomic_store_explicit(&pool->n_sleeping, 0, memory_order_relaxed); + + if (pthread_mutex_init(&pool->wake_lock, NULL) != 0) { + free(pool); + return NULL; + } + if (pthread_cond_init(&pool->wake_cond, NULL) != 0) { + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + free(pool); + return NULL; + } + + if (ext_init(&pool->ext, 1024) != 0) { + PTHREAD_CHECK(pthread_cond_destroy(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + free(pool); + return NULL; + } + + pool->workers = (worker_t *)calloc((size_t)nthreads, sizeof(worker_t)); + if (!pool->workers) { + ext_destroy(&pool->ext); + PTHREAD_CHECK(pthread_cond_destroy(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + free(pool); + return NULL; + } + + for (int i = 0; i < nthreads; i++) { + worker_t *w = &pool->workers[i]; + if (deque_init(&w->deque, DEQUE_DEFAULT_CAP) != 0) { + for (int j = 0; j < i; j++) + deque_destroy(&pool->workers[j].deque); + free(pool->workers); + ext_destroy(&pool->ext); + PTHREAD_CHECK(pthread_cond_destroy(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + free(pool); + return NULL; + } + w->pool = pool; + w->id = i; + w->rng = (unsigned)(i + 1) * 2654435761u; + } + + /* Default stacks are fine: LIFO pop bounds stack depth to + * O(tree_depth), which is a few KB at most. */ + for (int i = 0; i < nthreads; i++) { + if (pthread_create(&pool->workers[i].thread, NULL, + worker_main, &pool->workers[i]) != 0) { + atomic_store(&pool->shutdown, 1); + PTHREAD_CHECK(pthread_mutex_lock(&pool->wake_lock)); + PTHREAD_CHECK(pthread_cond_broadcast(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_unlock(&pool->wake_lock)); + for (int j = 0; j < i; j++) + PTHREAD_CHECK(pthread_join(pool->workers[j].thread, NULL)); + for (int j = 0; j < nthreads; j++) + deque_destroy(&pool->workers[j].deque); + free(pool->workers); + ext_destroy(&pool->ext); + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + PTHREAD_CHECK(pthread_cond_destroy(&pool->wake_cond)); + free(pool); + return NULL; + } + } + + return pool; +} + +void tp_destroy(threadpool_t *pool) +{ + if (!pool) return; + + atomic_store(&pool->shutdown, 1); + PTHREAD_CHECK(pthread_mutex_lock(&pool->wake_lock)); + PTHREAD_CHECK(pthread_cond_broadcast(&pool->wake_cond)); + PTHREAD_CHECK(pthread_mutex_unlock(&pool->wake_lock)); + + for (int i = 0; i < pool->nworkers; i++) + PTHREAD_CHECK(pthread_join(pool->workers[i].thread, NULL)); + + for (int i = 0; i < pool->nworkers; i++) { + /* Drain per-worker group free lists. */ + tp_group_t *g = pool->workers[i].free_groups; + while (g) { + tp_group_t *next = g->next; + free(g); + g = next; + } + deque_destroy(&pool->workers[i].deque); + } + free(pool->workers); + ext_destroy(&pool->ext); + PTHREAD_CHECK(pthread_mutex_destroy(&pool->wake_lock)); + PTHREAD_CHECK(pthread_cond_destroy(&pool->wake_cond)); + free(pool); +} + +int tp_get_nthreads(threadpool_t *pool) +{ + return pool ? pool->nworkers : 0; +} + +void tp_request_shutdown(threadpool_t *pool) +{ + if (!pool) return; + /* Only atomic store — safe to call from a signal handler. */ + atomic_store(&pool->shutdown, 1); +} + +/* ══════════════════════════════════════════════════════════════════ + * Public API: task groups + * ══════════════════════════════════════════════════════════════════ */ + +tp_group_t *tp_group_create(threadpool_t *pool) +{ + tp_group_t *g = NULL; + worker_t *w = tl_worker; + + /* Try per-worker free list — zero contention. */ + if (w && w->pool == pool && w->free_groups) { + g = w->free_groups; + w->free_groups = g->next; + w->n_free--; + } + + if (!g) { + g = (tp_group_t *)calloc(1, sizeof(*g)); + if (!g) return NULL; + } + + g->pool = pool; + atomic_store_explicit(&g->pending, 0, memory_order_relaxed); + return g; +} + +int tp_group_submit(tp_group_t *group, void (*fn)(void *), void *arg) +{ + atomic_fetch_add(&group->pending, 1); + tp_task_t task = { .fn = fn, .arg = arg, .pending = &group->pending }; + + worker_t *w = tl_worker; + int pushed = -1; + + if (w && w->pool == group->pool) + pushed = deque_push(&w->deque, task); + + if (pushed != 0) { + /* Non-worker or deque full — fall back to global queue. */ + if (ext_push(&group->pool->ext, task) != 0) { + atomic_fetch_sub(&group->pending, 1); + return -1; + } + } + + notify_workers(group->pool); + return 0; +} + +void tp_group_wait(tp_group_t *group) +{ + threadpool_t *pool = group->pool; + worker_t *w = tl_worker; + unsigned ext_rng = (unsigned)(uintptr_t)group * 2654435761u; + if (ext_rng == 0) ext_rng = 1; + unsigned spins = 0; + + while (atomic_load(&group->pending) > 0) { + if (atomic_load_explicit(&pool->shutdown, memory_order_relaxed)) + break; + + tp_task_t task; + int found; + + if (w && w->pool == pool) + found = (try_find_work(w, &task) == 0); + else + found = (try_find_work_ext(pool, &task, &ext_rng) == 0); + + if (found) { + execute_task(&task); + spins = 0; + } else if (++spins < 128) { + /* spin */ + } else { + sched_yield(); + spins = 0; + } + } +} + +void tp_group_destroy(tp_group_t *group) +{ + worker_t *w = tl_worker; + + if (w && w->pool == group->pool && w->n_free < MAX_FREE_GROUPS) { + group->next = w->free_groups; + w->free_groups = group; + w->n_free++; + } else { + free(group); + } +} + +/* ══════════════════════════════════════════════════════════════════ + * Public API: parallel for + * ══════════════════════════════════════════════════════════════════ */ + +struct pfor_chunk { + void (*fn)(int, int, void *); + void *arg; + int start; + int end; +}; + +static void pfor_worker(void *arg) +{ + struct pfor_chunk *c = (struct pfor_chunk *)arg; + c->fn(c->start, c->end, c->arg); +} + +#define MAX_STACK_CHUNKS 64 + +void tp_parallel_for_chunked(threadpool_t *pool, int start, int end, + int min_chunk_size, + void (*fn)(int, int, void *), void *arg) +{ + if (start >= end) return; + + int n = end - start; + int nchunks = pool->nworkers + 1; + if (nchunks > n) nchunks = n; + + /* Limit parallelism so each chunk has at least min_chunk_size iterations */ + if (min_chunk_size > 1) { + int max_chunks = (n + min_chunk_size - 1) / min_chunk_size; + if (nchunks > max_chunks) nchunks = max_chunks; + } + if (nchunks <= 1) { fn(start, end, arg); return; } + + struct pfor_chunk stack_chunks[MAX_STACK_CHUNKS]; + struct pfor_chunk *chunks = stack_chunks; + int heap = 0; + + if (nchunks > MAX_STACK_CHUNKS) { + chunks = (struct pfor_chunk *)calloc((size_t)nchunks, + sizeof(struct pfor_chunk)); + if (!chunks) { fn(start, end, arg); return; } + heap = 1; + } + + tp_group_t *group = tp_group_create(pool); + if (!group) { + if (heap) free(chunks); + fn(start, end, arg); + return; + } + + int chunk_size = (n + nchunks - 1) / nchunks; + int serial_from = end; /* if submit fails, run remaining chunks serially */ + for (int i = 0; i < nchunks; i++) { + int cs = start + i * chunk_size; + int ce = cs + chunk_size; + if (ce > end) ce = end; + if (cs >= end) break; + chunks[i].fn = fn; + chunks[i].arg = arg; + chunks[i].start = cs; + chunks[i].end = ce; + if (tp_group_submit(group, pfor_worker, &chunks[i]) != 0) { + serial_from = cs; + break; + } + } + + tp_group_wait(group); + tp_group_destroy(group); + + /* Execute any chunks that failed to submit. */ + if (serial_from < end) + fn(serial_from, end, arg); + + if (heap) free(chunks); +} + +void tp_parallel_for(threadpool_t *pool, int start, int end, + void (*fn)(int, int, void *), void *arg) +{ + tp_parallel_for_chunked(pool, start, end, 1, fn, arg); +} diff --git a/lib/src/threadpool/threadpool.h b/lib/src/threadpool/threadpool.h new file mode 100644 index 0000000..a4e3c97 --- /dev/null +++ b/lib/src/threadpool/threadpool.h @@ -0,0 +1,147 @@ +/* + * threadpool.h — Lightweight work-stealing thread pool. + * + * A POSIX-threads-based thread pool using Chase-Lev work-stealing deques. + * Designed for recursive divide-and-conquer workloads (guide-tree traversal, + * Hirschberg alignment) and data-parallel loops (distance matrices). + * + * Supports three parallelism patterns: + * + * 1. PARALLEL FOR — tp_parallel_for() + * Splits [start, end) into chunks, one per worker. The calling thread + * participates. Best for embarrassingly parallel loops. + * + * 2. FORK-JOIN — tp_group_submit() + tp_group_wait() + * Submit N independent tasks, then block until all complete. The + * waiting thread executes queued work while it waits. + * + * 3. RECURSIVE TASKS — groups created inside tasks (nested wait) + * A task can create a new group, submit children, and wait. LIFO + * deque ordering ensures the C stack depth is O(tree_depth), not + * O(tree_size). Safe for trees with millions of nodes. + * + * LIFECYCLE: + * threadpool_t *pool = tp_create(0); // 0 = auto-detect CPUs + * // ... use pool for parallel work ... + * tp_destroy(pool); // joins all workers, frees all memory + * + * THREAD SAFETY: + * - tp_create / tp_destroy: call from one thread only. + * - tp_group_*: a group must be used by one owner thread at a time. + * The owner creates it, submits tasks, waits, and destroys it. + * Tasks submitted to the group may run on any worker thread. + * - tp_parallel_for: safe to call from any thread, including from + * inside a task (nested parallelism). + * - tp_request_shutdown: safe to call from any thread or signal handler. + * + * ERROR HANDLING: + * - tp_create returns NULL on failure (out of memory, pthread errors). + * - tp_group_create returns NULL on out of memory. + * - tp_group_submit returns -1 on failure (out of memory); the task + * is NOT executed. Check the return value. + * - tp_parallel_for never fails: it falls back to serial execution + * if allocation or submission fails. + * + * GRACEFUL SHUTDOWN (signal handling): + * Call tp_request_shutdown(pool) to make tp_group_wait() return early. + * This function is async-signal-safe (it only writes an atomic flag). + * After shutdown, call tp_destroy(pool) from the main thread. + * + * USAGE EXAMPLES: + * + * // Pattern 1: Parallel for (distance matrix) + * void compute_row(int start, int end, void *arg) { + * float **dm = (float **)arg; + * for (int i = start; i < end; i++) + * dm[i] = compute_distances(i); + * } + * tp_parallel_for(pool, 0, num_seqs, compute_row, dm); + * + * // Pattern 2: Fork-join (two independent tasks) + * tp_group_t *g = tp_group_create(pool); + * tp_group_submit(g, forward_pass, &fwd_arg); + * tp_group_submit(g, backward_pass, &bwd_arg); + * tp_group_wait(g); + * tp_group_destroy(g); + * + * // Pattern 3: Recursive tasks (guide-tree traversal) + * void align_node(void *arg) { + * struct node *n = (struct node *)arg; + * if (is_leaf(n)) { align_leaf(n); return; } + * tp_group_t *g = tp_group_create(pool); + * tp_group_submit(g, align_node, n->left); + * tp_group_submit(g, align_node, n->right); + * tp_group_wait(g); + * tp_group_destroy(g); + * merge_alignments(n); + * } + */ + +#ifndef THREADPOOL_H +#define THREADPOOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct threadpool threadpool_t; +typedef struct tp_group tp_group_t; + +/* ── Pool lifecycle ─────────────────────────────────────────────── */ + +/* Create a pool with nthreads workers (0 = auto-detect CPU count). + * Returns NULL on failure. */ +threadpool_t *tp_create(int nthreads); + +/* Destroy the pool. Joins all worker threads and frees all memory. + * All task groups MUST have been waited on and destroyed first. */ +void tp_destroy(threadpool_t *pool); + +/* Request shutdown without blocking. Causes tp_group_wait() to + * return early and workers to exit. Follow with tp_destroy(). + * Async-signal-safe (writes an atomic flag only). */ +void tp_request_shutdown(threadpool_t *pool); + +/* Number of worker threads in the pool. */ +int tp_get_nthreads(threadpool_t *pool); + +/* ── Task groups ────────────────────────────────────────────────── */ + +/* Create a group for coordinating related tasks. + * Returns NULL on out of memory. */ +tp_group_t *tp_group_create(threadpool_t *pool); + +/* Submit fn(arg) to the pool under this group. + * Returns 0 on success, -1 on failure (task NOT executed). + * May be called from any thread, including from inside a task. */ +int tp_group_submit(tp_group_t *group, void (*fn)(void *), void *arg); + +/* Block until every task in the group has completed. + * The calling thread participates in executing queued work while waiting. + * Returns early if tp_request_shutdown() has been called. */ +void tp_group_wait(tp_group_t *group); + +/* Free a group. Must be called after tp_group_wait(). */ +void tp_group_destroy(tp_group_t *group); + +/* ── Parallel for ───────────────────────────────────────────────── */ + +/* Partition [start, end) into chunks and call fn(chunk_start, chunk_end, arg) + * for each chunk. The calling thread participates in the work. + * Falls back to serial execution on allocation failure (never fails). */ +void tp_parallel_for(threadpool_t *pool, int start, int end, + void (*fn)(int start, int end, void *arg), void *arg); + +/* Like tp_parallel_for, but limits parallelism so each chunk has at least + * min_chunk_size iterations. Useful for controlling overhead when per-iteration + * work is very small. min_chunk_size <= 1 is equivalent to tp_parallel_for. */ +void tp_parallel_for_chunked(threadpool_t *pool, int start, int end, + int min_chunk_size, + void (*fn)(int start, int end, void *arg), + void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* THREADPOOL_H */ diff --git a/pyproject.toml b/pyproject.toml index 4ca86ec..85dff44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build" [project] name = "kalign-python" -version = "3.5.1" +version = "3.5.2" description = "Python wrapper for the Kalign multiple sequence alignment engine" readme = "README-python.md" license = "Apache-2.0" @@ -69,7 +69,7 @@ skbio = ["scikit-bio>=0.6.3"] io = ["biopython>=1.85"] # For I/O helper functions analysis = ["pandas>=2.3.0", "matplotlib>=3.9.4", "seaborn>=0.13.2"] all = ["biopython>=1.85", "scikit-bio>=0.6.3", "pandas>=2.3.0", "matplotlib>=3.9.4", "seaborn>=0.13.2"] -benchmark = ["dash>=2.14", "plotly>=5.18", "pandas>=2.0", "tqdm>=4.60"] +benchmark = ["dash>=2.14", "plotly>=5.18", "pandas>=2.0", "tqdm>=4.60", "pymoo>=0.6", "rich>=13.0", "kneed>=0.8", "optuna>=3.0"] # Development dependencies dev = [ @@ -161,24 +161,22 @@ before-all = [ ] repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" -# macOS settings +# macOS settings — OpenMP disabled to avoid libomp.dylib conflicts with conda/numpy [tool.cibuildwheel.macos] before-all = [ "brew install cmake || echo 'cmake already installed'", ] repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" +[tool.cibuildwheel.macos.config-settings] +"cmake.args" = "-DUSE_OPENMP=OFF;-DUSE_THREADPOOL=ON" + # Windows settings [tool.cibuildwheel.windows] before-all = [ "choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' || echo 'cmake already installed'", ] -# Test settings for all platforms -[tool.cibuildwheel.environment] -# Set OpenMP variables for better performance -OMP_NUM_THREADS = "1" - [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] diff --git a/python-kalign/__init__.py b/python-kalign/__init__.py index 8c588dc..d352a9c 100644 --- a/python-kalign/__init__.py +++ b/python-kalign/__init__.py @@ -7,6 +7,7 @@ import os import threading +import warnings from importlib import import_module from typing import Any, List, Literal, Optional, Union @@ -67,36 +68,65 @@ def __repr__(self): PROTEIN_PFASUM60 = _core.PROTEIN_PFASUM60 PROTEIN_PFASUM_AUTO = _core.PROTEIN_PFASUM_AUTO PROTEIN_DIVERGENT = _core.PROTEIN_DIVERGENT +PROTEIN_CORBLOSUM66 = _core.PROTEIN_CORBLOSUM66 AUTO = _core.AUTO -# Refinement mode constants +# Refinement mode constants (used by ensemble_custom_file_to_file optimizer path) REFINE_NONE = _core.REFINE_NONE REFINE_ALL = _core.REFINE_ALL REFINE_CONFIDENT = _core.REFINE_CONFIDENT REFINE_INLINE = _core.REFINE_INLINE # Mode constants -MODE_DEFAULT = "default" MODE_FAST = "fast" -MODE_PRECISE = "precise" - -# Mode preset definitions -_MODE_PRESETS = { - "default": {"vsm_amax": -1.0, "consistency": 5, "consistency_weight": 2.0}, - "fast": {"vsm_amax": -1.0, "consistency": 0, "consistency_weight": 2.0}, - "precise": { - "vsm_amax": -1.0, - "ensemble": 3, - "realign": 1, - "consistency": 0, - "consistency_weight": 2.0, - }, -} +MODE_DEFAULT = "default" +MODE_RECALL = "recall" +MODE_ACCURATE = "accurate" + +# Valid preset modes (resolved by C library) +_PRESET_MODES = {"fast", "default", "recall", "accurate"} # Global thread control _thread_local = threading.local() _default_threads = 1 +# Sequence type string->int mapping (shared by all entry points) +_SEQ_TYPE_MAP = { + "auto": AUTO, + "dna": DNA, + "rna": RNA, + "protein": PROTEIN, + "pfasum43": PROTEIN_PFASUM43, + "pfasum60": PROTEIN_PFASUM60, + "pfasum": PROTEIN_PFASUM_AUTO, + "divergent": PROTEIN_DIVERGENT, + "internal": DNA_INTERNAL, +} + + +def _resolve_seq_type(seq_type): + """Convert string or int seq_type to integer constant.""" + if isinstance(seq_type, int): + return seq_type + lower = seq_type.lower() + if lower not in _SEQ_TYPE_MAP: + raise ValueError( + f"Invalid seq_type: {seq_type}. Must be one of: {list(_SEQ_TYPE_MAP.keys())}" + ) + return _SEQ_TYPE_MAP[lower] + + +def _resolve_mode_name(mode): + """Normalize mode name to one of the C-library presets.""" + if mode is None: + return "default" + lower = mode.lower() + if lower not in _PRESET_MODES: + raise ValueError( + f"Invalid mode: {mode!r}. Must be one of: 'fast', 'default', 'recall', 'accurate'" + ) + return lower + def _conf_to_pp(conf: float) -> str: """Convert a confidence value [0..1] to HMMER-style PP character.""" @@ -106,10 +136,7 @@ def _conf_to_pp(conf: float) -> str: def _confidence_to_pp_string(seq: str, confidences: list) -> str: - """Convert per-residue confidence array to PP string. - - Gap positions get '.', residues get HMMER-style PP characters. - """ + """Convert per-residue confidence array to PP string.""" pp = [] for ch, conf in zip(seq, confidences): if ch == "-" or ch == ".": @@ -119,112 +146,40 @@ def _confidence_to_pp_string(seq: str, confidences: list) -> str: return "".join(pp) -def align( - sequences: List[str], - seq_type: Union[str, int] = "auto", - gap_open: Optional[float] = None, - gap_extend: Optional[float] = None, - terminal_gap_extend: Optional[float] = None, - n_threads: Optional[int] = None, - refine: Union[str, int] = "none", - ensemble: int = 0, - min_support: int = 0, - seq_weights: float = 0.0, - consistency: int = 5, - consistency_weight: float = 2.0, - vsm_amax: float = -1.0, - realign: int = 0, - ensemble_seed: int = 42, - mode: Optional[str] = None, - fmt: Literal["plain", "biopython", "skbio"] = "plain", - ids: Optional[List[str]] = None, -) -> Union[List[str], Any]: - """ - Multiple sequence alignment via Kalign. +def _parse_refine_mode(refine): + """Convert string or int refine mode to integer constant.""" + if isinstance(refine, int): + return refine + refine_map = { + "none": REFINE_NONE, + "all": REFINE_ALL, + "confident": REFINE_CONFIDENT, + "inline": REFINE_INLINE, + } + refine_lower = refine.lower() + if refine_lower not in refine_map: + raise ValueError( + f"Invalid refine mode: {refine}. Must be one of: {list(refine_map.keys())}" + ) + return refine_map[refine_lower] - Parameters - ---------- - sequences : list of str - List of sequences to align. Sequences should be provided as strings - containing the sequence characters (e.g., 'ATCG' for DNA, 'ACGU' for RNA, - or amino acid codes for proteins). - seq_type : str or int, optional - Sequence type specification. Can be: - - "auto" or AUTO: Auto-detect sequence type (default) - - "dna" or DNA: DNA sequences - - "rna" or RNA: RNA sequences - - "protein" or PROTEIN: Protein sequences - - "divergent" or PROTEIN_DIVERGENT: Divergent protein sequences - - "internal" or DNA_INTERNAL: DNA with internal gap preference - gap_open : float, optional - Gap opening penalty (positive value, e.g. 5.5). If None, uses Kalign defaults. - gap_extend : float, optional - Gap extension penalty (positive value, e.g. 2.0). If None, uses Kalign defaults. - terminal_gap_extend : float, optional - Terminal gap extension penalty (positive value, e.g. 1.0). If None, uses Kalign defaults. - n_threads : int, optional - Number of threads to use for alignment. If None, uses global default. - refine : str or int, optional - Refinement mode: "none", "all", "confident", or "inline" (default: "none"). - ensemble : int, optional - Number of ensemble runs (default: 0 = off). Set to e.g. 3 for higher - accuracy at the cost of ~10x runtime. - vsm_amax : float, optional - Variable scoring matrix amplitude (default: -1.0 = use kalign defaults: - 2.0 for protein, 0.0 for DNA/RNA). Set to 0.0 to disable. - realign : int, optional - Number of alignment-guided tree rebuild iterations (default: 0 = off). - ensemble_seed : int, optional - RNG seed for ensemble runs (default: 42). - mode : str, optional - Preset mode: "default" (consistency+VSM), "fast" (VSM only), - "precise" (ensemble+VSM+realign). Explicit parameters override - mode defaults. None is treated as "default". - fmt : {'plain', 'biopython', 'skbio'}, default 'plain' - Choose return-object flavour: - - 'plain': list of aligned sequences (fastest) - - 'biopython': Bio.Align.MultipleSeqAlignment object - - 'skbio': skbio.TabularMSA object - ids : list of str, optional - Sequence IDs (used only for Biopython / scikit-bio objects). - If None, generates 'seq0', 'seq1', etc. - Returns - ------- - list of str | Bio.Align.MultipleSeqAlignment | skbio.TabularMSA - Aligned sequences. Return type depends on `fmt` parameter. - - Raises - ------ - ValueError - If input sequences are empty or invalid - RuntimeError - If alignment fails - ImportError - If Biopython or scikit-bio are requested but not installed - - Examples - -------- - >>> import kalign - >>> sequences = ["ATCGATCGATCG", "ATCGTCGATCG", "ATCGATCATCG"] - - # 1) Plain list (default) - >>> aligned = kalign.align(sequences) - >>> print(aligned) - ['ATCGATCGATCG', 'ATCG-TCGATCG', 'ATCGATC-ATCG'] - - # 2) Biopython object - >>> aln_bp = kalign.align(sequences, fmt="biopython", ids=["s1","s2","s3"]) - >>> print(type(aln_bp)) - - - # 3) scikit-bio object - >>> aln_sk = kalign.align(sequences, fmt="skbio") - >>> print(type(aln_sk)) - - """ +def _infer_skbio_type(sequences, skbio_seq): + """Infer the appropriate skbio sequence class from raw sequence content.""" + chars = set() + for seq in sequences: + chars.update(seq.upper().replace("-", "").replace(".", "")) + dna_chars = set("ACGTNRYSWKMBDHV") + rna_chars = set("ACGUNRYSWKMBDHV") + if "U" in chars and "T" not in chars and chars <= rna_chars: + return skbio_seq.RNA + if chars <= dna_chars: + return skbio_seq.DNA + return skbio_seq.Protein - # Input validation - replicate C CLI robustness + +def _validate_sequences(sequences): + """Validate input sequences, raising on empty/invalid input.""" if not sequences: raise ValueError("No sequences were found in the input") @@ -236,7 +191,6 @@ def align( if not all(isinstance(seq, str) for seq in sequences): raise ValueError("All sequences must be strings") - # Check for empty or whitespace-only sequences empty_sequences = [] for i, seq in enumerate(sequences): if not seq or not seq.strip(): @@ -252,22 +206,16 @@ def align( f"Sequences at indices {empty_sequences} are empty or contain only whitespace" ) - # Check for valid sequence characters (basic validation) for i, seq in enumerate(sequences): - # Remove common whitespace and check if anything remains cleaned_seq = "".join(seq.split()) if len(cleaned_seq) == 0: raise ValueError( f"Sequence at index {i} contains only whitespace characters" ) - - # Check for obviously invalid characters (control characters, etc) if any(ord(char) < 32 for char in cleaned_seq if char not in "\t\n\r"): raise ValueError( f"Sequence at index {i} contains invalid control characters" ) - - # Check for digits and other problematic characters that cause platform-specific segfaults invalid_chars = set(char for char in cleaned_seq if char.isdigit()) if invalid_chars: raise ValueError( @@ -275,67 +223,79 @@ def align( f"Sequences should only contain valid biological sequence characters." ) - # Warn about very short sequences (like C CLI warnings) very_short_sequences = [ i for i, seq in enumerate(sequences) if len(seq.strip()) < 3 ] if very_short_sequences and len(very_short_sequences) > len(sequences) * 0.5: - import warnings - warnings.warn( - f"Many sequences are very short (< 3 characters). This may affect alignment quality.", + "Many sequences are very short (< 3 characters). This may affect alignment quality.", UserWarning, - stacklevel=2, + stacklevel=3, ) - # Convert string sequence types to integers - seq_type_map = { - "auto": AUTO, - "dna": DNA, - "rna": RNA, - "protein": PROTEIN, - "pfasum43": PROTEIN_PFASUM43, - "pfasum60": PROTEIN_PFASUM60, - "pfasum": PROTEIN_PFASUM_AUTO, - "divergent": PROTEIN_DIVERGENT, - "internal": DNA_INTERNAL, - } - if isinstance(seq_type, str): - seq_type_lower = seq_type.lower() - if seq_type_lower not in seq_type_map: +def align( + sequences: List[str], + seq_type: Union[str, int] = "auto", + gap_open: Optional[float] = None, + gap_extend: Optional[float] = None, + terminal_gap_extend: Optional[float] = None, + n_threads: Optional[int] = None, + mode: Optional[str] = None, + fmt: Literal["plain", "biopython", "skbio"] = "plain", + ids: Optional[List[str]] = None, +) -> Union[List[str], Any]: + """ + Multiple sequence alignment via Kalign. + + Parameters + ---------- + sequences : list of str + List of sequences to align. + seq_type : str or int, optional + Sequence type: "auto", "dna", "rna", "protein" (default: "auto") + gap_open : float, optional + Gap opening penalty. When set, mode is ignored and "fast" preset + is used with the specified penalty. + gap_extend : float, optional + Gap extension penalty. + terminal_gap_extend : float, optional + Terminal gap extension penalty. + n_threads : int, optional + Number of threads (default: global setting). + mode : str, optional + Preset mode: "fast", "default", "accurate" (default: "default"). + fmt : {'plain', 'biopython', 'skbio'}, default 'plain' + Return format. + ids : list of str, optional + Sequence IDs (for Biopython/scikit-bio formats). + + Returns + ------- + list of str | Bio.Align.MultipleSeqAlignment | skbio.TabularMSA + """ + _validate_sequences(sequences) + + seq_type_int = _resolve_seq_type(seq_type) + + # Parameter validation for gap penalties + if gap_open is not None: + if not isinstance(gap_open, (int, float)): + raise ValueError("gap_open must be a number") + if gap_open < 0: + raise ValueError("gap_open must be a positive number (penalty value)") + if gap_extend is not None: + if not isinstance(gap_extend, (int, float)): + raise ValueError("gap_extend must be a number") + if gap_extend < 0: + raise ValueError("gap_extend must be a positive number (penalty value)") + if terminal_gap_extend is not None: + if not isinstance(terminal_gap_extend, (int, float)): + raise ValueError("terminal_gap_extend must be a number") + if terminal_gap_extend < 0: raise ValueError( - f"Invalid seq_type: {seq_type}. Must be one of: {list(seq_type_map.keys())}" + "terminal_gap_extend must be a positive number (penalty value)" ) - seq_type_int = seq_type_map[seq_type_lower] - else: - seq_type_int = seq_type - - # Parameter validation and defaults - # The C core expects positive penalty values (e.g., gpo=5.5, gpe=2.0). - # A value of -1.0 signals "use defaults". - if gap_open is None: - gap_open = -1.0 - elif not isinstance(gap_open, (int, float)): - raise ValueError("gap_open must be a number") - elif gap_open < 0: - raise ValueError("gap_open must be a positive number (penalty value)") - - if gap_extend is None: - gap_extend = -1.0 - elif not isinstance(gap_extend, (int, float)): - raise ValueError("gap_extend must be a number") - elif gap_extend < 0: - raise ValueError("gap_extend must be a positive number (penalty value)") - - if terminal_gap_extend is None: - terminal_gap_extend = -1.0 - elif not isinstance(terminal_gap_extend, (int, float)): - raise ValueError("terminal_gap_extend must be a number") - elif terminal_gap_extend < 0: - raise ValueError( - "terminal_gap_extend must be a positive number (penalty value)" - ) # Handle thread count if n_threads is None: @@ -344,84 +304,55 @@ def align( raise ValueError("n_threads must be an integer") elif n_threads < 1: raise ValueError("n_threads must be at least 1") - elif n_threads > 1024: # reasonable upper limit - import warnings - + elif n_threads > 1024: warnings.warn( - f"Very high thread count ({n_threads}) may not improve performance and could cause issues.", + f"Very high thread count ({n_threads}) may not improve performance.", UserWarning, stacklevel=2, ) - # Resolve mode presets — collect explicitly-set params - _explicit = {} - # We detect "explicit" by checking if the value differs from the function signature default. - # For mode-relevant params, the signature defaults match the "default" mode preset. - if ensemble != 0: - _explicit["ensemble"] = ensemble - if realign != 0: - _explicit["realign"] = realign - if consistency != 5: - _explicit["consistency"] = consistency - if consistency_weight != 2.0: - _explicit["consistency_weight"] = consistency_weight - if vsm_amax != -1.0: - _explicit["vsm_amax"] = vsm_amax - - resolved = _resolve_mode(mode, _explicit) - ensemble = resolved.get("ensemble", ensemble) - realign = resolved.get("realign", realign) - consistency = resolved.get("consistency", consistency) - consistency_weight = resolved.get("consistency_weight", consistency_weight) - vsm_amax = resolved.get("vsm_amax", vsm_amax) - - # Convert refine mode - refine_int = _parse_refine_mode(refine) - - # Validate ensemble - if not isinstance(ensemble, int) or ensemble < 0: - raise ValueError("ensemble must be a non-negative integer") - - # Call the C++ binding for core alignment + # Gap penalty override rule: if any gap penalty is set, use "fast" preset + has_gap_override = ( + gap_open is not None + or gap_extend is not None + or terminal_gap_extend is not None + ) + if has_gap_override: + effective_mode = "fast" + else: + effective_mode = _resolve_mode_name(mode) + confidence_data = None - try: - result = _core.align( - sequences, - seq_type_int, - gap_open, - gap_extend, - terminal_gap_extend, - n_threads, - refine_int, - ensemble, - min_support, - float(seq_weights), - consistency, - consistency_weight, - vsm_amax, - realign, - ensemble_seed, - ) - # When ensemble is used, result is (sequences, confidence_dict) - if isinstance(result, tuple): - aligned_seqs = result[0] - confidence_data = result[1] - else: - aligned_seqs = result - except Exception as e: - raise RuntimeError(f"Alignment failed: {str(e)}") + result = _core.align_mode( + sequences, + effective_mode, + seq_type_int, + gap_open if gap_open is not None else -1.0, + gap_extend if gap_extend is not None else -1.0, + terminal_gap_extend if terminal_gap_extend is not None else -1.0, + n_threads, + ) + if isinstance(result, tuple): + aligned_seqs = result[0] + confidence_data = result[1] + else: + aligned_seqs = result + + return _format_result( + aligned_seqs, sequences, seq_type_int, confidence_data, fmt, ids + ) + - # Validate IDs if provided (applies to all formats) +def _format_result(aligned_seqs, sequences, seq_type_int, confidence_data, fmt, ids): + """Format alignment result based on requested format.""" if ids is not None and len(ids) != len(aligned_seqs): raise ValueError( f"Number of IDs ({len(ids)}) must match number of sequences ({len(aligned_seqs)})" ) - # Handle different return formats if fmt == "plain": return aligned_seqs - # Generate IDs if not provided (only for ecosystem formats) if ids is None: ids = [f"seq{i}" for i in range(len(aligned_seqs))] @@ -438,14 +369,12 @@ def align( records = [] for idx, (s, i) in enumerate(zip(aligned_seqs, ids)): rec = SeqRecord(Seq(s), id=i) - # Attach per-residue confidence as letter_annotations if available if confidence_data is not None: res_conf = confidence_data["residue_confidence"] if idx < len(res_conf) and len(res_conf[idx]) == len(s): pp_str = _confidence_to_pp_string(s, res_conf[idx]) rec.letter_annotations["posterior_probability"] = pp_str records.append(rec) - return MultipleSeqAlignment(records) if fmt == "skbio": @@ -458,7 +387,6 @@ def align( "scikit-bio not installed. Run: pip install kalign-python[skbio]" ) from e - # Select the appropriate skbio sequence type skbio_type_map = { DNA: skbio_seq.DNA, DNA_INTERNAL: skbio_seq.DNA, @@ -469,7 +397,6 @@ def align( if seq_type_int in skbio_type_map: SeqClass = skbio_type_map[seq_type_int] else: - # AUTO or unknown: infer from sequence content SeqClass = _infer_skbio_type(sequences, skbio_seq) return TabularMSA( @@ -479,116 +406,17 @@ def align( raise ValueError(f"Unknown fmt='{fmt}' (expected 'plain', 'biopython', 'skbio')") -def _parse_refine_mode(refine): - """Convert string or int refine mode to integer constant.""" - if isinstance(refine, int): - return refine - refine_map = { - "none": REFINE_NONE, - "all": REFINE_ALL, - "confident": REFINE_CONFIDENT, - "inline": REFINE_INLINE, - } - refine_lower = refine.lower() - if refine_lower not in refine_map: - raise ValueError( - f"Invalid refine mode: {refine}. Must be one of: {list(refine_map.keys())}" - ) - return refine_map[refine_lower] - - -def _infer_skbio_type(sequences, skbio_seq): - """Infer the appropriate skbio sequence class from raw sequence content.""" - chars = set() - for seq in sequences: - chars.update(seq.upper().replace("-", "").replace(".", "")) - dna_chars = set("ACGTNRYSWKMBDHV") - rna_chars = set("ACGUNRYSWKMBDHV") - if "U" in chars and "T" not in chars and chars <= rna_chars: - return skbio_seq.RNA - if chars <= dna_chars: - return skbio_seq.DNA - return skbio_seq.Protein - - -def _resolve_mode(mode, explicit_kwargs): - """Resolve mode presets, letting explicit parameters override. - - Parameters - ---------- - mode : str or None - One of "default", "fast", "precise", or None (treated as "default"). - explicit_kwargs : dict - Only keys that the caller *explicitly* passed (not sentinel/default). - - Returns - ------- - dict - Merged parameter values: mode defaults + explicit overrides. - """ - if mode is None: - mode = "default" - mode_lower = mode.lower() - if mode_lower not in _MODE_PRESETS: - raise ValueError( - f"Invalid mode: {mode!r}. Must be one of: 'default', 'fast', 'precise'" - ) - result = dict(_MODE_PRESETS[mode_lower]) - result.update(explicit_kwargs) - return result - - def set_num_threads(n: int) -> None: - """ - Set the default number of threads for alignment operations. - - This affects all future calls to align() that don't explicitly specify n_threads. - The setting is thread-local, so different threads can have different defaults. - - Parameters - ---------- - n : int - Number of threads to use. Must be at least 1. - - Raises - ------ - ValueError - If n is less than 1 - - Examples - -------- - >>> import kalign - >>> kalign.set_num_threads(4) - >>> aligned = kalign.align(sequences) # Uses 4 threads - """ + """Set the default number of threads for alignment operations.""" global _default_threads if n < 1: raise ValueError("Number of threads must be at least 1") - - # Use thread-local storage for thread safety _thread_local.num_threads = n - # Also update global default for new threads _default_threads = n def get_num_threads() -> int: - """ - Get the current default number of threads for alignment operations. - - Returns - ------- - int - Current default number of threads - - Examples - -------- - >>> import kalign - >>> kalign.get_num_threads() - 1 - >>> kalign.set_num_threads(8) - >>> kalign.get_num_threads() - 8 - """ + """Get the current default number of threads.""" return getattr(_thread_local, "num_threads", _default_threads) @@ -599,19 +427,6 @@ def align_from_file( gap_extend: Optional[float] = None, terminal_gap_extend: Optional[float] = None, n_threads: Optional[int] = None, - refine: Union[str, int] = "none", - adaptive_budget: bool = False, - ensemble: int = 0, - ensemble_seed: int = 42, - dist_scale: float = 0.0, - vsm_amax: float = -1.0, - min_support: int = 0, - realign: int = 0, - save_poar: str = "", - load_poar: str = "", - seq_weights: float = 0.0, - consistency: int = 5, - consistency_weight: float = 2.0, mode: Optional[str] = None, ) -> AlignedSequences: """ @@ -620,138 +435,62 @@ def align_from_file( Parameters ---------- input_file : str - Path to input file containing sequences. Supported formats: - FASTA, MSF, Clustal, aligned FASTA. + Path to input file (FASTA, MSF, Clustal). seq_type : str or int, optional - Sequence type specification (same as align function) - gap_open : float, optional - Gap opening penalty - gap_extend : float, optional - Gap extension penalty - terminal_gap_extend : float, optional - Terminal gap extension penalty + Sequence type (default: "auto"). + gap_open, gap_extend, terminal_gap_extend : float, optional + Gap penalties. When set, mode is ignored and "fast" preset is used. n_threads : int, optional - Number of threads to use for alignment - vsm_amax : float, optional - Variable scoring matrix a_max parameter (default: -1.0 = use - kalign defaults: 2.0 for protein, 0.0 for DNA/RNA). Set to 0.0 - to disable. When > 0, subtracts max(0, amax - d) from all - substitution scores, where d is the estimated pairwise distance. + Number of threads. + mode : str, optional + Preset mode: "fast", "default", "accurate" (default: "default"). Returns ------- AlignedSequences - Named tuple with ``names`` and ``sequences`` fields. - - Raises - ------ - FileNotFoundError - If input file doesn't exist - RuntimeError - If alignment fails """ - if not os.path.exists(input_file): raise FileNotFoundError(f"Input file not found: {input_file}") - # Convert string sequence types to integers - seq_type_map = { - "auto": AUTO, - "dna": DNA, - "rna": RNA, - "protein": PROTEIN, - "pfasum43": PROTEIN_PFASUM43, - "pfasum60": PROTEIN_PFASUM60, - "pfasum": PROTEIN_PFASUM_AUTO, - "divergent": PROTEIN_DIVERGENT, - "internal": DNA_INTERNAL, - } - - if isinstance(seq_type, str): - seq_type_lower = seq_type.lower() - if seq_type_lower not in seq_type_map: - raise ValueError( - f"Invalid seq_type: {seq_type}. Must be one of: {list(seq_type_map.keys())}" - ) - seq_type_int = seq_type_map[seq_type_lower] - else: - seq_type_int = seq_type + seq_type_int = _resolve_seq_type(seq_type) - # Use defaults if not specified - if gap_open is None: - gap_open = -1.0 - if gap_extend is None: - gap_extend = -1.0 - if terminal_gap_extend is None: - terminal_gap_extend = -1.0 - - # Handle thread count if n_threads is None: n_threads = get_num_threads() if n_threads < 1: raise ValueError("n_threads must be at least 1") - # Resolve mode presets - _explicit = {} - if ensemble != 0: - _explicit["ensemble"] = ensemble - if realign != 0: - _explicit["realign"] = realign - if consistency != 5: - _explicit["consistency"] = consistency - if consistency_weight != 2.0: - _explicit["consistency_weight"] = consistency_weight - if vsm_amax != -1.0: - _explicit["vsm_amax"] = vsm_amax - - resolved = _resolve_mode(mode, _explicit) - ensemble = resolved.get("ensemble", ensemble) - realign = resolved.get("realign", realign) - consistency = resolved.get("consistency", consistency) - consistency_weight = resolved.get("consistency_weight", consistency_weight) - vsm_amax = resolved.get("vsm_amax", vsm_amax) - - # Convert refine mode - refine_int = _parse_refine_mode(refine) - - # Call the C++ binding — returns (names, sequences) or (names, sequences, confidence) - try: - result = _core.align_from_file( - input_file, - seq_type_int, - gap_open, - gap_extend, - terminal_gap_extend, - n_threads, - refine_int, - int(adaptive_budget), - ensemble, - ensemble_seed, - dist_scale, - vsm_amax, - min_support, - realign, - save_poar, - load_poar, - float(seq_weights), - consistency, - consistency_weight, + has_gap_override = ( + gap_open is not None + or gap_extend is not None + or terminal_gap_extend is not None + ) + if has_gap_override: + effective_mode = "fast" + else: + effective_mode = _resolve_mode_name(mode) + + result = _core.align_from_file_mode( + input_file, + effective_mode, + seq_type_int, + gap_open if gap_open is not None else -1.0, + gap_extend if gap_extend is not None else -1.0, + terminal_gap_extend if terminal_gap_extend is not None else -1.0, + n_threads, + ) + if len(result) == 3: + names, sequences, conf = result + col_conf = list(conf["column_confidence"]) + res_conf = [list(row) for row in conf["residue_confidence"]] + return AlignedSequences( + names=names, + sequences=sequences, + column_confidence=col_conf, + residue_confidence=res_conf, ) - if len(result) == 3: - names, sequences, conf = result - col_conf = list(conf["column_confidence"]) - res_conf = [list(row) for row in conf["residue_confidence"]] - return AlignedSequences( - names=names, - sequences=sequences, - column_confidence=col_conf, - residue_confidence=res_conf, - ) - else: - names, sequences = result - return AlignedSequences(names=names, sequences=sequences) - except Exception as e: - raise RuntimeError(f"Alignment failed: {str(e)}") + else: + names, sequences = result + return AlignedSequences(names=names, sequences=sequences) def write_alignment( @@ -775,27 +514,11 @@ def write_alignment( Output format: "fasta", "clustal", "stockholm", "phylip" (default: "fasta") ids : list of str, optional Sequence IDs. If None, generates seq0, seq1, etc. - - Raises - ------ - ValueError - If invalid format or empty sequence list - ImportError - If Biopython is not installed for non-FASTA formats - - Examples - -------- - >>> aligned = kalign.align(sequences) - >>> kalign.write_alignment(aligned, "output.fasta") - >>> kalign.write_alignment(aligned, "output.aln", format="clustal", ids=["seq1", "seq2"]) """ - if not sequences: raise ValueError("Empty sequence list provided") format_lower = format.lower() - - # Map format aliases format_map = { "fasta": "fasta", "fa": "fasta", @@ -806,23 +529,20 @@ def write_alignment( "phylip": "phylip", "phy": "phylip", } - if format_lower not in format_map: raise ValueError( f"Invalid format: {format}. Must be one of: fasta, clustal, stockholm, phylip" ) - mapped_format = format_map[format_lower] - # Use appropriate writer from io module (lazy import to avoid circular imports) - from . import io + from . import io as _io if mapped_format == "fasta": - io.write_fasta(sequences, output_file, ids=ids) + _io.write_fasta(sequences, output_file, ids=ids) elif mapped_format == "clustal": - io.write_clustal(sequences, output_file, ids=ids) + _io.write_clustal(sequences, output_file, ids=ids) elif mapped_format == "stockholm": - io.write_stockholm( + _io.write_stockholm( sequences, output_file, ids=ids, @@ -830,58 +550,13 @@ def write_alignment( residue_confidence=residue_confidence, ) elif mapped_format == "phylip": - io.write_phylip(sequences, output_file, ids=ids) + _io.write_phylip(sequences, output_file, ids=ids) def generate_test_sequences( n_seq: int, n_obs: int, dna: bool, length: int, seed: int = 42 ) -> List[str]: - """ - Generate test sequences using DSSim HMM-based simulator. - - This function uses the DSSim (Dynamic Sequence Simulator) from the Kalign - test suite to generate realistic evolutionary sequence data for testing - and benchmarking purposes. - - Parameters - ---------- - n_seq : int - Number of sequences to generate - n_obs : int - Number of observed sequences for training the HMM (typically 20-50) - dna : bool - True to generate DNA sequences, False to generate protein sequences - length : int - Target sequence length - seed : int, optional - Random seed for reproducible results (default: 42) - - Returns - ------- - list of str - List of generated sequences - - Raises - ------ - RuntimeError - If sequence generation fails - - Examples - -------- - >>> import kalign - >>> # Generate 100 DNA sequences of length 200 - >>> dna_seqs = kalign.generate_test_sequences(100, 20, True, 200) - >>> len(dna_seqs) - 100 - >>> len(dna_seqs[0]) - 200 - - >>> # Generate 50 protein sequences of length 150 - >>> protein_seqs = kalign.generate_test_sequences(50, 30, False, 150) - >>> len(protein_seqs) - 50 - """ - + """Generate test sequences using DSSim HMM-based simulator.""" if n_seq < 1: raise ValueError("n_seq must be at least 1") if n_obs < 1: @@ -889,45 +564,30 @@ def generate_test_sequences( if length < 1: raise ValueError("length must be at least 1") - try: - sequences = _core.generate_test_sequences(n_seq, n_obs, dna, length, seed) - return sequences - except Exception as e: - raise RuntimeError(f"Test sequence generation failed: {str(e)}") + sequences = _core.generate_test_sequences(n_seq, n_obs, dna, length, seed) + return sequences def compare(reference_file: str, test_file: str) -> float: """ Compare two multiple sequence alignments and return SP score. - Computes the sum-of-pairs (SP) score between a reference alignment - and a test alignment. Sequences are matched by name, so both files - must contain the same sequences. - Parameters ---------- reference_file : str - Path to reference alignment file (FASTA, MSF, or Clustal format) + Path to reference alignment file test_file : str Path to test alignment file Returns ------- float - SP score (0-100), where 100 means identical alignments - - Raises - ------ - FileNotFoundError - If either file doesn't exist - RuntimeError - If comparison fails + SP score (0-100) """ if not os.path.exists(reference_file): raise FileNotFoundError(f"Reference file not found: {reference_file}") if not os.path.exists(test_file): raise FileNotFoundError(f"Test file not found: {test_file}") - return _core.compare(reference_file, test_file) @@ -938,38 +598,23 @@ def compare_detailed( column_mask: Optional[List[int]] = None, ) -> dict: """ - Compare two multiple sequence alignments returning detailed POAR scores. - - Computes BAliBASE-compatible recall (SP), precision, F1, and TC scores. - Only "core" columns (gap fraction <= max_gap_frac in reference) are scored - for recall/TC. Gap-gap matches are NOT counted. + Compare two MSAs returning detailed POAR scores (recall, precision, F1, TC). Parameters ---------- reference_file : str - Path to reference alignment file (FASTA, MSF, or Clustal format) + Path to reference alignment file test_file : str Path to test alignment file max_gap_frac : float, optional - Maximum gap fraction for a reference column to be scored (default: 0.2). - Use -1.0 to score all columns regardless of gap content. - Ignored when column_mask is provided. + Max gap fraction for scored columns (default: 0.2). column_mask : list of int, optional - Explicit binary mask (0/1) for each column in the reference alignment. - When provided, only columns with mask=1 are scored (overrides max_gap_frac). - Typically parsed from BAliBASE XML core block annotations. + Explicit binary mask for column scoring. Returns ------- dict Keys: recall, precision, f1, tc, ref_pairs, test_pairs, common_pairs - - Raises - ------ - FileNotFoundError - If either file doesn't exist - RuntimeError - If comparison fails """ if not os.path.exists(reference_file): raise FileNotFoundError(f"Reference file not found: {reference_file}") @@ -978,7 +623,6 @@ def compare_detailed( if column_mask is not None: return _core.compare_detailed_with_mask(reference_file, test_file, column_mask) - return _core.compare_detailed(reference_file, test_file, max_gap_frac) @@ -991,140 +635,220 @@ def align_file_to_file( gap_extend: Optional[float] = None, terminal_gap_extend: Optional[float] = None, n_threads: Optional[int] = None, - refine: Union[str, int] = "none", - adaptive_budget: bool = False, - ensemble: int = 0, - ensemble_seed: int = 42, - dist_scale: float = 0.0, - vsm_amax: float = -1.0, - min_support: int = 0, - realign: int = 0, - save_poar: str = "", - load_poar: str = "", - seq_weights: float = 0.0, - consistency: int = 5, - consistency_weight: float = 2.0, mode: Optional[str] = None, ) -> None: """ Align sequences from input file and write result to output file. - Unlike align_from_file(), this preserves all sequence metadata (names, - descriptions), which is required for MSA comparison with compare(). - Parameters ---------- input_file : str - Path to input file containing unaligned sequences + Path to input file output_file : str Path to output alignment file format : str, optional Output format: "fasta", "msf", "clu" (default: "fasta") seq_type : str or int, optional Sequence type (default: "auto") - gap_open : float, optional - Gap opening penalty - gap_extend : float, optional - Gap extension penalty - terminal_gap_extend : float, optional - Terminal gap extension penalty + gap_open, gap_extend, terminal_gap_extend : float, optional + Gap penalties. When set, mode is ignored and "fast" preset is used. n_threads : int, optional - Number of threads (default: uses global setting) - vsm_amax : float, optional - Variable scoring matrix a_max parameter (default: -1.0 = use - kalign defaults: 2.0 for protein, 0.0 for DNA/RNA). Set to 0.0 - to disable. - - Raises - ------ - FileNotFoundError - If input file doesn't exist - RuntimeError - If alignment or writing fails + Number of threads. + mode : str, optional + Preset mode: "fast", "default", "accurate" (default: "default"). """ if not os.path.exists(input_file): raise FileNotFoundError(f"Input file not found: {input_file}") - seq_type_map = { - "auto": AUTO, - "dna": DNA, - "rna": RNA, - "protein": PROTEIN, - "pfasum43": PROTEIN_PFASUM43, - "pfasum60": PROTEIN_PFASUM60, - "pfasum": PROTEIN_PFASUM_AUTO, - "divergent": PROTEIN_DIVERGENT, - "internal": DNA_INTERNAL, - } + seq_type_int = _resolve_seq_type(seq_type) - if isinstance(seq_type, str): - seq_type_lower = seq_type.lower() - if seq_type_lower not in seq_type_map: - raise ValueError( - f"Invalid seq_type: {seq_type}. Must be one of: {list(seq_type_map.keys())}" - ) - seq_type_int = seq_type_map[seq_type_lower] - else: - seq_type_int = seq_type - - if gap_open is None: - gap_open = -1.0 - if gap_extend is None: - gap_extend = -1.0 - if terminal_gap_extend is None: - terminal_gap_extend = -1.0 if n_threads is None: n_threads = get_num_threads() - # Resolve mode presets - _explicit = {} - if ensemble != 0: - _explicit["ensemble"] = ensemble - if realign != 0: - _explicit["realign"] = realign - if consistency != 5: - _explicit["consistency"] = consistency - if consistency_weight != 2.0: - _explicit["consistency_weight"] = consistency_weight - if vsm_amax != -1.0: - _explicit["vsm_amax"] = vsm_amax - - resolved = _resolve_mode(mode, _explicit) - ensemble = resolved.get("ensemble", ensemble) - realign = resolved.get("realign", realign) - consistency = resolved.get("consistency", consistency) - consistency_weight = resolved.get("consistency_weight", consistency_weight) - vsm_amax = resolved.get("vsm_amax", vsm_amax) - - refine_int = _parse_refine_mode(refine) - - _core.align_file_to_file( + has_gap_override = ( + gap_open is not None + or gap_extend is not None + or terminal_gap_extend is not None + ) + if has_gap_override: + effective_mode = "fast" + else: + effective_mode = _resolve_mode_name(mode) + + _core.align_file_to_file_mode( input_file, output_file, + effective_mode, format, - seq_type_int, - gap_open, - gap_extend, - terminal_gap_extend, n_threads, - refine_int, - int(adaptive_budget), - ensemble, - ensemble_seed, - dist_scale, - vsm_amax, - min_support, - realign, - save_poar, - load_poar, - float(seq_weights), - consistency, - consistency_weight, + seq_type_int, + gap_open if gap_open is not None else -1.0, + gap_extend if gap_extend is not None else -1.0, + terminal_gap_extend if terminal_gap_extend is not None else -1.0, ) # Convenience aliases -kalign = align # For backward compatibility or alternative naming +kalign = align + + +# --------------------------------------------------------------------------- +# Confidence masking utilities +# --------------------------------------------------------------------------- + + +def mask_alignment( + result: AlignedSequences, + threshold: float = 0.5, + style: str = "lowercase", +) -> AlignedSequences: + """Mask low-confidence columns in an alignment result. + + Parameters + ---------- + result : AlignedSequences + Alignment result with confidence scores (from ensemble modes). + threshold : float + Columns with confidence below this value are masked. + style : str + "lowercase" — uncertain residues become lowercase (default). + "remove" — uncertain residues replaced with '-'. + + Returns + ------- + AlignedSequences + New result with masked sequences. Confidence arrays unchanged. + """ + if result.column_confidence is None: + import warnings + + warnings.warn( + "No confidence scores available (requires ensemble mode). " + "Returning unmasked alignment." + ) + return result + + conf = result.column_confidence + masked_seqs = [] + for seq in result.sequences: + chars = list(seq) + for col in range(len(chars)): + if col < len(conf) and conf[col] < threshold and chars[col] != "-": + if style == "remove": + chars[col] = "-" + else: + chars[col] = chars[col].lower() + masked_seqs.append("".join(chars)) + + return AlignedSequences( + names=result.names, + sequences=masked_seqs, + column_confidence=result.column_confidence, + residue_confidence=result.residue_confidence, + ) + + +def filter_alignment( + result: AlignedSequences, + threshold: float = 0.5, +) -> AlignedSequences: + """Remove low-confidence columns from an alignment result. + + Parameters + ---------- + result : AlignedSequences + Alignment result with confidence scores (from ensemble modes). + threshold : float + Columns with confidence below this value are removed entirely. + + Returns + ------- + AlignedSequences + New result with only high-confidence columns. Shorter sequences. + """ + if result.column_confidence is None: + import warnings + + warnings.warn( + "No confidence scores available (requires ensemble mode). " + "Returning unfiltered alignment." + ) + return result + + conf = result.column_confidence + keep = [col for col in range(len(conf)) if conf[col] >= threshold] + + filtered_seqs = [] + for seq in result.sequences: + filtered_seqs.append("".join(seq[col] for col in keep)) + + filtered_conf = [conf[col] for col in keep] + filtered_res_conf = None + if result.residue_confidence is not None: + filtered_res_conf = [ + [row[col] for col in keep] for row in result.residue_confidence + ] + + return AlignedSequences( + names=result.names, + sequences=filtered_seqs, + column_confidence=filtered_conf, + residue_confidence=filtered_res_conf, + ) + + +def add_to_alignment( + existing: str, + new_sequences: str, + output: str, + format: str = "fasta", + n_threads: Optional[int] = None, +) -> None: + """Add new sequences to an existing alignment. + + Each new sequence is aligned against the consensus profile of the + existing alignment. The existing sequences are NOT re-aligned — their + gaps are preserved exactly. + + Parameters + ---------- + existing : str + Path to existing alignment file (FASTA/MSF/Clustal). + new_sequences : str + Path to file with new unaligned sequences. + output : str + Path to output file (existing + new sequences, all aligned). + format : str, optional + Output format: "fasta", "msf", "clu" (default: "fasta"). + n_threads : int, optional + Number of threads. + """ + if not os.path.exists(existing): + raise FileNotFoundError(f"Existing alignment not found: {existing}") + if not os.path.exists(new_sequences): + raise FileNotFoundError(f"New sequences file not found: {new_sequences}") + + if n_threads is None: + n_threads = get_num_threads() + + _core.add_to_alignment_file(existing, new_sequences, output, format, n_threads) + + +def write_confidence(path: str, result: AlignedSequences) -> None: + """Write per-column confidence scores to a text file. + + Parameters + ---------- + path : str + Output file path. + result : AlignedSequences + Alignment result with confidence scores. + """ + if result.column_confidence is None: + raise ValueError("No confidence scores available (requires ensemble mode)") + with open(path, "w") as f: + for val in result.column_confidence: + f.write(f"{val:.4f}\n") __all__ = [ @@ -1139,6 +863,10 @@ def align_file_to_file( "get_num_threads", "kalign", "AlignedSequences", + "mask_alignment", + "filter_alignment", + "write_confidence", + "add_to_alignment", "DNA", "DNA_INTERNAL", "RNA", @@ -1147,14 +875,16 @@ def align_file_to_file( "PROTEIN_PFASUM60", "PROTEIN_PFASUM_AUTO", "PROTEIN_DIVERGENT", + "PROTEIN_CORBLOSUM66", "AUTO", "REFINE_NONE", "REFINE_ALL", "REFINE_CONFIDENT", "REFINE_INLINE", - "MODE_DEFAULT", "MODE_FAST", - "MODE_PRECISE", + "MODE_DEFAULT", + "MODE_RECALL", + "MODE_ACCURATE", "__version__", "__author__", "__email__", diff --git a/python-kalign/_core.cpp b/python-kalign/_core.cpp index f84fba5..ad2a46d 100644 --- a/python-kalign/_core.cpp +++ b/python-kalign/_core.cpp @@ -2,12 +2,11 @@ #include #include #include +#include #include #include #include #include -#include -#include #include // Include DSSim for sequence simulation and MSA structures @@ -16,6 +15,7 @@ extern "C" { #include "msa_alloc.h" #include "msa_op.h" #include "msa_cmp.h" + #include "aln_add.h" #include "dssim.h" } @@ -68,235 +68,6 @@ static py::object extract_confidence(struct msa* msa_data, int numseq) { return result; } -// Shared alignment routing helper — selects the appropriate kalign function -// based on the combination of parameters provided. -static int run_alignment(struct msa* msa_data, int n_threads, int seq_type, - float gap_open, float gap_extend, float terminal_gap_extend, - int refine, int adaptive_budget, - int ensemble, uint64_t ensemble_seed, - float dist_scale, float vsm_amax, - int min_support, int realign, - const std::string& save_poar, - const std::string& load_poar, - float use_seq_weights = -1.0f, - int consistency_anchors = 0, float consistency_weight = 2.0f) -{ - if (!load_poar.empty()) { - return kalign_consensus_from_poar(msa_data, load_poar.c_str(), - min_support > 0 ? min_support : 2); - } else if (ensemble > 0) { - const char* save_path = save_poar.empty() ? nullptr : save_poar.c_str(); - return kalign_ensemble(msa_data, n_threads, seq_type, ensemble, - gap_open, gap_extend, terminal_gap_extend, - ensemble_seed, min_support, save_path, - refine, dist_scale, vsm_amax, realign, - use_seq_weights, - consistency_anchors, consistency_weight); - } else if (realign > 0) { - return kalign_run_realign(msa_data, n_threads, seq_type, gap_open, gap_extend, terminal_gap_extend, refine, adaptive_budget, dist_scale, vsm_amax, realign, use_seq_weights, consistency_anchors, consistency_weight); - } else if (consistency_anchors > 0) { - return kalign_run_seeded(msa_data, n_threads, seq_type, gap_open, gap_extend, terminal_gap_extend, refine, adaptive_budget, 0, 0.0f, dist_scale, vsm_amax, use_seq_weights, consistency_anchors, consistency_weight); - } else if (dist_scale > 0.0f || vsm_amax >= 0.0f || use_seq_weights >= 0.0f) { - return kalign_run_dist_scale(msa_data, n_threads, seq_type, gap_open, gap_extend, terminal_gap_extend, refine, adaptive_budget, dist_scale, vsm_amax, use_seq_weights); - } else { - return kalign_run(msa_data, n_threads, seq_type, gap_open, gap_extend, terminal_gap_extend, refine, adaptive_budget); - } -} - -// Main alignment function -py::object align_sequences( - const std::vector& sequences, - int seq_type = KALIGN_TYPE_UNDEFINED, - float gap_open = -1.0f, - float gap_extend = -1.0f, - float terminal_gap_extend = -1.0f, - int n_threads = 1, - int refine = KALIGN_REFINE_NONE, - int ensemble = 0, - int min_support = 0, - float seq_weights = -1.0f, - int consistency_anchors = 0, float consistency_weight = 2.0f, - float vsm_amax = -1.0f, - int realign = 0, - uint64_t ensemble_seed = 42 -) { - if (sequences.empty()) { - throw std::invalid_argument("Empty sequence list provided"); - } - - // Convert Python strings to C format - std::vector seq_ptrs; - std::vector seq_lengths; - seq_ptrs.reserve(sequences.size()); - seq_lengths.reserve(sequences.size()); - - for (const auto& seq : sequences) { - seq_ptrs.push_back(const_cast(seq.c_str())); - seq_lengths.push_back(static_cast(seq.length())); - } - - if (n_threads < 1) { - n_threads = 1; - } - - // Build msa struct from input arrays - struct msa* msa_data = nullptr; - int result = kalign_arr_to_msa(seq_ptrs.data(), seq_lengths.data(), - static_cast(sequences.size()), &msa_data); - if (result != 0 || !msa_data) { - throw std::runtime_error("Failed to create MSA from input sequences"); - } - msa_data->quiet = 1; - - // Route to appropriate alignment function - result = run_alignment(msa_data, n_threads, seq_type, - gap_open, gap_extend, terminal_gap_extend, - refine, 0, - ensemble, ensemble_seed, - 0.0f, vsm_amax, - min_support, realign, - "", "", - seq_weights, - consistency_anchors, consistency_weight); - - if (result != 0) { - kalign_free_msa(msa_data); - throw std::runtime_error("Kalign alignment failed with error code: " + std::to_string(result)); - } - - // Extract confidence data before converting to arrays (which frees the MSA) - py::object confidence = extract_confidence(msa_data, static_cast(sequences.size())); - - // Extract aligned sequences - char** aligned_seqs = nullptr; - int alignment_length = 0; - result = kalign_msa_to_arr(msa_data, &aligned_seqs, &alignment_length); - kalign_free_msa(msa_data); - - if (result != 0 || !aligned_seqs) { - throw std::runtime_error("Failed to extract aligned sequences"); - } - - // Convert results back to Python - auto seqs = c_strings_to_python(aligned_seqs, static_cast(sequences.size()), alignment_length); - - // If ensemble was used and confidence data exists, return tuple - if (ensemble > 0 && !confidence.is_none()) { - return py::make_tuple(seqs, confidence); - } - - // Otherwise return just sequences (backward compat) - return py::cast(seqs); -} - -// File-based alignment function — returns (names, sequences) or (names, sequences, confidence) -py::object align_from_file( - const std::string& input_file, - int seq_type = KALIGN_TYPE_UNDEFINED, - float gap_open = -1.0f, - float gap_extend = -1.0f, - float terminal_gap_extend = -1.0f, - int n_threads = 1, - int refine = KALIGN_REFINE_NONE, - int adaptive_budget = 0, - int ensemble = 0, - uint64_t ensemble_seed = 42, - float dist_scale = 0.0f, - float vsm_amax = -1.0f, - int min_support = 0, - int realign = 0, - const std::string& save_poar = "", - const std::string& load_poar = "", - float seq_weights = -1.0f, - int consistency_anchors = 0, float consistency_weight = 2.0f -) { - struct msa* msa_data = nullptr; - - // Read input file - int result = kalign_read_input(const_cast(input_file.c_str()), &msa_data, 1); - if (result != 0) { - throw std::runtime_error("Failed to read input file: " + input_file); - } - - // Check if msa_data is NULL - this happens when the file format cannot be detected - if (!msa_data) { - throw std::runtime_error("Could not detect valid sequence format in file: " + input_file); - } - - // Perform alignment - result = run_alignment(msa_data, n_threads, seq_type, - gap_open, gap_extend, terminal_gap_extend, - refine, adaptive_budget, - ensemble, ensemble_seed, - dist_scale, vsm_amax, - min_support, realign, - save_poar, load_poar, - seq_weights, - consistency_anchors, consistency_weight); - if (result != 0) { - kalign_free_msa(msa_data); - throw std::runtime_error("Kalign alignment failed with error code: " + std::to_string(result)); - } - - // Extract confidence data before writing (which doesn't preserve it) - py::object confidence = extract_confidence(msa_data, msa_data->numseq); - - // Write to a temporary file so kalign_write_msa handles gap insertion - const char* tmpdir = std::getenv("TMPDIR"); - if (!tmpdir) tmpdir = std::getenv("TMP"); - if (!tmpdir) tmpdir = std::getenv("TEMP"); - if (!tmpdir) tmpdir = "/tmp"; - std::string temp_file = std::string(tmpdir) + "/kalign_output.fa"; - result = kalign_write_msa(msa_data, const_cast(temp_file.c_str()), const_cast("fasta")); - - kalign_free_msa(msa_data); - - if (result != 0) { - throw std::runtime_error("Failed to write alignment results"); - } - - // Parse the FASTA file, capturing both headers (names) and sequences - std::ifstream file(temp_file); - std::vector names; - std::vector aligned_sequences; - std::string line, current_name, current_seq; - - while (std::getline(file, line)) { - if (line.empty()) continue; - - if (line[0] == '>') { - if (!current_seq.empty()) { - names.push_back(current_name); - aligned_sequences.push_back(current_seq); - current_seq.clear(); - } - // Strip the '>' prefix; take everything up to the first whitespace as the name - current_name = line.substr(1); - auto ws = current_name.find_first_of(" \t"); - if (ws != std::string::npos) { - current_name = current_name.substr(0, ws); - } - } else { - current_seq += line; - } - } - - if (!current_seq.empty()) { - names.push_back(current_name); - aligned_sequences.push_back(current_seq); - } - - // Clean up temp file - std::remove(temp_file.c_str()); - - // If confidence data exists, return 3-tuple - if (!confidence.is_none()) { - return py::make_tuple(names, aligned_sequences, confidence); - } - - return py::make_tuple(names, aligned_sequences); -} - // Generate test sequences using DSSim std::vector generate_test_sequences( int n_seq, @@ -445,51 +216,359 @@ py::dict compare_detailed_with_mask_files(const std::string& reference_file, return d; } -// Align sequences from input file and write result to output file, preserving all metadata -void align_file_to_file( +// Ensemble with per-run parameters — playground for optimization. +// Each run gets its own gap penalties, matrix type, and tree noise. +// Now uses kalign_align_full with per-run configs. +void ensemble_custom_file_to_file( const std::string& input_file, const std::string& output_file, + const std::vector& run_gpo, + const std::vector& run_gpe, + const std::vector& run_tgpe, + const std::vector& run_noise, + const std::vector& run_types = {}, const std::string& format = "fasta", - int seq_type = KALIGN_TYPE_UNDEFINED, - float gap_open = -1.0f, - float gap_extend = -1.0f, - float terminal_gap_extend = -1.0f, - int n_threads = 1, + int seq_type = KALIGN_TYPE_PROTEIN, + uint64_t seed = 42, + int min_support = 0, int refine = KALIGN_REFINE_NONE, - int adaptive_budget = 0, - int ensemble = 0, - uint64_t ensemble_seed = 42, - float dist_scale = 0.0f, float vsm_amax = -1.0f, - int min_support = 0, int realign = 0, - const std::string& save_poar = "", - const std::string& load_poar = "", float seq_weights = -1.0f, - int consistency_anchors = 0, float consistency_weight = 2.0f + int n_threads = 1, + int consistency_anchors = 0, + float consistency_weight = 2.0f, + // Per-run overrides: when non-empty, override the shared value per-run. + // Same pattern as run_types: empty = use shared value for all runs. + const std::vector& run_vsm_amax = {}, + const std::vector& run_seq_weights = {}, + const std::vector& run_refine = {}, + const std::vector& run_realign = {}, + const std::vector& run_consistency_anchors = {}, + const std::vector& run_consistency_weight = {}, + int consistency_merge = 0, + float consistency_merge_weight = 2.0f +) { + int n_runs = static_cast(run_gpo.size()); + if (n_runs < 1) { + throw std::invalid_argument("Must provide at least 1 run"); + } + if (static_cast(run_gpe.size()) != n_runs || + static_cast(run_tgpe.size()) != n_runs || + static_cast(run_noise.size()) != n_runs) { + throw std::invalid_argument("All per-run arrays must have the same length"); + } + // Validate optional per-run arrays: must be empty or same length as run_gpo + if (!run_types.empty() && static_cast(run_types.size()) != n_runs) { + throw std::invalid_argument("run_types must be empty or same length as run_gpo"); + } + if (!run_vsm_amax.empty() && static_cast(run_vsm_amax.size()) != n_runs) { + throw std::invalid_argument("run_vsm_amax must be empty or same length as run_gpo"); + } + if (!run_seq_weights.empty() && static_cast(run_seq_weights.size()) != n_runs) { + throw std::invalid_argument("run_seq_weights must be empty or same length as run_gpo"); + } + if (!run_refine.empty() && static_cast(run_refine.size()) != n_runs) { + throw std::invalid_argument("run_refine must be empty or same length as run_gpo"); + } + if (!run_realign.empty() && static_cast(run_realign.size()) != n_runs) { + throw std::invalid_argument("run_realign must be empty or same length as run_gpo"); + } + if (!run_consistency_anchors.empty() && static_cast(run_consistency_anchors.size()) != n_runs) { + throw std::invalid_argument("run_consistency_anchors must be empty or same length as run_gpo"); + } + if (!run_consistency_weight.empty() && static_cast(run_consistency_weight.size()) != n_runs) { + throw std::invalid_argument("run_consistency_weight must be empty or same length as run_gpo"); + } + + struct msa* msa_data = nullptr; + int result = kalign_read_input(const_cast(input_file.c_str()), &msa_data, 1); + if (result != 0 || !msa_data) { + throw std::runtime_error("Failed to read input file: " + input_file); + } + + /* Build per-run configs. Each optional per-run array overrides the + shared scalar when non-empty, following the run_types pattern. */ + std::vector runs(n_runs); + for (int k = 0; k < n_runs; k++) { + runs[k] = kalign_run_config_defaults(); + runs[k].matrix = (!run_types.empty()) ? run_types[k] : seq_type; + runs[k].gpo = run_gpo[k]; + runs[k].gpe = run_gpe[k]; + runs[k].tgpe = run_tgpe[k]; + runs[k].tree_seed = seed + static_cast(k); + runs[k].tree_noise = run_noise[k]; + runs[k].vsm_amax = (!run_vsm_amax.empty()) ? run_vsm_amax[k] : vsm_amax; + runs[k].dist_scale = 0.0f; + runs[k].seq_weights = (!run_seq_weights.empty()) ? run_seq_weights[k] : seq_weights; + runs[k].refine = (!run_refine.empty()) ? run_refine[k] : refine; + runs[k].realign = (!run_realign.empty()) ? run_realign[k] : realign; + runs[k].consistency_anchors = (!run_consistency_anchors.empty()) ? run_consistency_anchors[k] : consistency_anchors; + runs[k].consistency_weight = (!run_consistency_weight.empty()) ? run_consistency_weight[k] : consistency_weight; + } + + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + ens.min_support = min_support; + ens.consistency_merge = consistency_merge; + ens.consistency_merge_weight = consistency_merge_weight; + + result = kalign_align_full(msa_data, runs.data(), n_runs, &ens, n_threads); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Ensemble alignment failed with error code: " + std::to_string(result)); + } + + result = kalign_write_msa(msa_data, const_cast(output_file.c_str()), + const_cast(format.c_str())); + kalign_free_msa(msa_data); + + if (result != 0) { + throw std::runtime_error("Failed to write output file: " + output_file); + } +} + +// Align in-memory sequences using a named mode preset. +// Detects biotype from sequences and delegates to C preset system. +py::object align_mode( + const std::vector& sequences, + const std::string& mode, + int seq_type = KALIGN_TYPE_UNDEFINED, + float gap_open = -1.0f, + float gap_extend = -1.0f, + float terminal_gap_extend = -1.0f, + int n_threads = 1 +) { + if (sequences.empty()) { + throw std::invalid_argument("Empty sequence list provided"); + } + + std::vector seq_ptrs; + std::vector seq_lengths; + seq_ptrs.reserve(sequences.size()); + seq_lengths.reserve(sequences.size()); + for (const auto& seq : sequences) { + seq_ptrs.push_back(const_cast(seq.c_str())); + seq_lengths.push_back(static_cast(seq.length())); + } + if (n_threads < 1) n_threads = 1; + + struct msa* msa_data = nullptr; + int result = kalign_arr_to_msa(seq_ptrs.data(), seq_lengths.data(), + static_cast(sequences.size()), &msa_data); + if (result != 0 || !msa_data) { + throw std::runtime_error("Failed to create MSA from input sequences"); + } + msa_data->quiet = 1; + + /* Force biotype if caller specified one */ + if (seq_type == KALIGN_MATRIX_DNA || seq_type == KALIGN_MATRIX_DNA_INTERNAL || + seq_type == KALIGN_MATRIX_NUC_1PAM || seq_type == KALIGN_MATRIX_NUC_20PAM || + seq_type == KALIGN_MATRIX_NUC_200PAM) { + msa_data->biotype = ALN_BIOTYPE_DNA; + } else if (seq_type == KALIGN_MATRIX_RNA) { + msa_data->biotype = ALN_BIOTYPE_DNA; /* RNA uses DNA biotype internally */ + } else if (seq_type != KALIGN_MATRIX_AUTO && seq_type != KALIGN_TYPE_UNDEFINED) { + msa_data->biotype = ALN_BIOTYPE_PROTEIN; + } + + /* Detect biotype if not already set */ + if (msa_data->biotype == ALN_BIOTYPE_UNDEF) { + result = detect_alphabet(msa_data); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Failed to detect sequence type"); + } + } + + /* Get preset configs */ + struct kalign_run_config runs[KALIGN_MAX_PRESET_RUNS]; + struct kalign_ensemble_config ens; + int n_runs = 0; + + result = kalign_get_mode_preset(mode.c_str(), msa_data->biotype, + runs, &n_runs, &ens); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::invalid_argument("Unknown mode: " + mode); + } + + /* Apply user gap penalty overrides to all runs */ + for (int k = 0; k < n_runs; k++) { + if (gap_open >= 0.0f) runs[k].gpo = gap_open; + if (gap_extend >= 0.0f) runs[k].gpe = gap_extend; + if (terminal_gap_extend >= 0.0f) runs[k].tgpe = terminal_gap_extend; + } + + result = kalign_align_full(msa_data, runs, n_runs, + n_runs > 1 ? &ens : nullptr, n_threads); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Alignment failed with error code: " + std::to_string(result)); + } + + py::object confidence = extract_confidence(msa_data, static_cast(sequences.size())); + + char** aligned_seqs = nullptr; + int alignment_length = 0; + result = kalign_msa_to_arr(msa_data, &aligned_seqs, &alignment_length); + kalign_free_msa(msa_data); + + if (result != 0 || !aligned_seqs) { + throw std::runtime_error("Failed to extract aligned sequences"); + } + + auto seqs = c_strings_to_python(aligned_seqs, static_cast(sequences.size()), alignment_length); + + if (n_runs > 1 && !confidence.is_none()) { + return py::make_tuple(seqs, confidence); + } + return py::cast(seqs); +} + +// Align from file using a named mode preset, returning (names, sequences). +py::object align_from_file_mode( + const std::string& input_file, + const std::string& mode, + int seq_type = KALIGN_TYPE_UNDEFINED, + float gap_open = -1.0f, + float gap_extend = -1.0f, + float terminal_gap_extend = -1.0f, + int n_threads = 1 ) { struct msa* msa_data = nullptr; + int result = kalign_read_input(const_cast(input_file.c_str()), &msa_data, 1); + if (result != 0 || !msa_data) { + throw std::runtime_error("Failed to read input file: " + input_file); + } + + /* Force biotype if caller specified one */ + if (seq_type == KALIGN_MATRIX_DNA || seq_type == KALIGN_MATRIX_DNA_INTERNAL) { + msa_data->biotype = ALN_BIOTYPE_DNA; + } else if (seq_type == KALIGN_MATRIX_RNA) { + msa_data->biotype = ALN_BIOTYPE_DNA; + } else if (seq_type != KALIGN_MATRIX_AUTO && seq_type != KALIGN_TYPE_UNDEFINED) { + msa_data->biotype = ALN_BIOTYPE_PROTEIN; + } + if (msa_data->biotype == ALN_BIOTYPE_UNDEF) { + result = detect_alphabet(msa_data); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Failed to detect sequence type"); + } + } + + struct kalign_run_config runs[KALIGN_MAX_PRESET_RUNS]; + struct kalign_ensemble_config ens; + int n_runs = 0; + + result = kalign_get_mode_preset(mode.c_str(), msa_data->biotype, + runs, &n_runs, &ens); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::invalid_argument("Unknown mode: " + mode); + } + + for (int k = 0; k < n_runs; k++) { + if (gap_open >= 0.0f) runs[k].gpo = gap_open; + if (gap_extend >= 0.0f) runs[k].gpe = gap_extend; + if (terminal_gap_extend >= 0.0f) runs[k].tgpe = terminal_gap_extend; + } + + result = kalign_align_full(msa_data, runs, n_runs, + n_runs > 1 ? &ens : nullptr, n_threads); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Alignment failed with error code: " + std::to_string(result)); + } + + py::object confidence = extract_confidence(msa_data, msa_data->numseq); + + int numseq = msa_data->numseq; + int alnlen = msa_data->alnlen; + + std::vector names; + std::vector aligned_sequences; + names.reserve(numseq); + aligned_sequences.reserve(numseq); + + for (int i = 0; i < numseq; i++) { + names.emplace_back(msa_data->sequences[i]->name); + aligned_sequences.emplace_back(msa_data->sequences[i]->seq, alnlen); + } + + kalign_free_msa(msa_data); + + if (!confidence.is_none()) { + return py::make_tuple(names, aligned_sequences, confidence); + } + return py::make_tuple(names, aligned_sequences); +} + +// Align file-to-file using a named mode preset (fast/default/recall/accurate). +// The C library provides NSGA-III optimized presets per biotype. +void align_file_to_file_mode( + const std::string& input_file, + const std::string& output_file, + const std::string& mode, + const std::string& format = "fasta", + int n_threads = 1, + int seq_type = KALIGN_TYPE_UNDEFINED, + float gap_open = -1.0f, + float gap_extend = -1.0f, + float terminal_gap_extend = -1.0f +) { + struct msa* msa_data = nullptr; int result = kalign_read_input(const_cast(input_file.c_str()), &msa_data, 1); if (result != 0 || !msa_data) { throw std::runtime_error("Failed to read input file: " + input_file); } - result = run_alignment(msa_data, n_threads, seq_type, - gap_open, gap_extend, terminal_gap_extend, - refine, adaptive_budget, - ensemble, ensemble_seed, - dist_scale, vsm_amax, - min_support, realign, - save_poar, load_poar, - seq_weights, - consistency_anchors, consistency_weight); + /* Force biotype if caller specified one */ + if (seq_type == KALIGN_MATRIX_DNA || seq_type == KALIGN_MATRIX_DNA_INTERNAL) { + msa_data->biotype = ALN_BIOTYPE_DNA; + } else if (seq_type == KALIGN_MATRIX_RNA) { + msa_data->biotype = ALN_BIOTYPE_DNA; + } else if (seq_type != KALIGN_MATRIX_AUTO && seq_type != KALIGN_TYPE_UNDEFINED) { + msa_data->biotype = ALN_BIOTYPE_PROTEIN; + } + + /* Detect biotype from sequences so we can select the right preset grid slot */ + if (msa_data->biotype == ALN_BIOTYPE_UNDEF) { + result = detect_alphabet(msa_data); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::runtime_error("Failed to detect sequence type"); + } + } + + struct kalign_run_config runs[KALIGN_MAX_PRESET_RUNS]; + struct kalign_ensemble_config ens; + int n_runs = 0; + + result = kalign_get_mode_preset(mode.c_str(), msa_data->biotype, + runs, &n_runs, &ens); + if (result != 0) { + kalign_free_msa(msa_data); + throw std::invalid_argument("Unknown mode: " + mode); + } + + /* Apply user gap penalty overrides to all runs */ + for (int k = 0; k < n_runs; k++) { + if (gap_open >= 0.0f) runs[k].gpo = gap_open; + if (gap_extend >= 0.0f) runs[k].gpe = gap_extend; + if (terminal_gap_extend >= 0.0f) runs[k].tgpe = terminal_gap_extend; + } + + result = kalign_align_full(msa_data, runs, n_runs, + n_runs > 1 ? &ens : nullptr, n_threads); if (result != 0) { kalign_free_msa(msa_data); throw std::runtime_error("Alignment failed with error code: " + std::to_string(result)); } - result = kalign_write_msa(msa_data, const_cast(output_file.c_str()), const_cast(format.c_str())); + result = kalign_write_msa(msa_data, const_cast(output_file.c_str()), + const_cast(format.c_str())); kalign_free_msa(msa_data); if (result != 0) { @@ -500,83 +579,6 @@ void align_file_to_file( PYBIND11_MODULE(_core, m) { m.doc() = "Python bindings for Kalign multiple sequence alignment"; - // Main alignment function - m.def("align", &align_sequences, - py::arg("sequences"), - py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, - py::arg("gap_open") = -1.0f, - py::arg("gap_extend") = -1.0f, - py::arg("terminal_gap_extend") = -1.0f, - py::arg("n_threads") = 1, - py::arg("refine") = KALIGN_REFINE_NONE, - py::arg("ensemble") = 0, - py::arg("min_support") = 0, - py::arg("seq_weights") = -1.0f, - py::arg("consistency_anchors") = 0, - py::arg("consistency_weight") = 2.0f, - py::arg("vsm_amax") = -1.0f, - py::arg("realign") = 0, - py::arg("ensemble_seed") = (uint64_t)42, - R"pbdoc( - Align a list of sequences using Kalign. - - Parameters - ---------- - sequences : list of str - List of sequences to align - seq_type : int, optional - Sequence type (default: auto-detect) - gap_open : float, optional - Gap opening penalty (default: -1.0, uses Kalign defaults) - gap_extend : float, optional - Gap extension penalty (default: -1.0, uses Kalign defaults) - terminal_gap_extend : float, optional - Terminal gap extension penalty (default: -1.0, uses Kalign defaults) - n_threads : int, optional - Number of threads to use (default: 1) - refine : int, optional - Refinement mode (default: REFINE_NONE) - ensemble : int, optional - Number of ensemble runs (default: 0 = off) - min_support : int, optional - Explicit consensus threshold (default: 0 = auto) - vsm_amax : float, optional - Variable scoring matrix amplitude (default: -1.0, uses Kalign defaults) - realign : int, optional - Number of realignment iterations (default: 0 = off) - ensemble_seed : int, optional - RNG seed for ensemble (default: 42) - - Returns - ------- - list of str or tuple - When ensemble > 0: (aligned_seqs, confidence_dict) - Otherwise: aligned sequences - )pbdoc"); - - // File-based alignment — returns (names, sequences) or (names, sequences, confidence) - m.def("align_from_file", &align_from_file, - py::arg("input_file"), - py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, - py::arg("gap_open") = -1.0f, - py::arg("gap_extend") = -1.0f, - py::arg("terminal_gap_extend") = -1.0f, - py::arg("n_threads") = 1, - py::arg("refine") = KALIGN_REFINE_NONE, - py::arg("adaptive_budget") = 0, - py::arg("ensemble") = 0, - py::arg("ensemble_seed") = (uint64_t)42, - py::arg("dist_scale") = 0.0f, - py::arg("vsm_amax") = -1.0f, - py::arg("min_support") = 0, - py::arg("realign") = 0, - py::arg("save_poar") = "", - py::arg("load_poar") = "", - py::arg("seq_weights") = -1.0f, - py::arg("consistency_anchors") = 0, - py::arg("consistency_weight") = 2.0f, - "Align sequences from a file. Returns (names, sequences) or (names, sequences, confidence) tuple."); - // Generate test sequences using DSSim m.def("generate_test_sequences", &generate_test_sequences, py::arg("n_seq"), @@ -674,34 +676,41 @@ PYBIND11_MODULE(_core, m) { Keys: recall, precision, f1, tc, ref_pairs, test_pairs, common_pairs )pbdoc"); - // Align file to file (preserves sequence names/metadata) - m.def("align_file_to_file", &align_file_to_file, + // Ensemble with per-run parameters (optimization playground) + m.def("ensemble_custom_file_to_file", &ensemble_custom_file_to_file, py::arg("input_file"), py::arg("output_file"), + py::arg("run_gpo"), + py::arg("run_gpe"), + py::arg("run_tgpe"), + py::arg("run_noise"), + py::arg("run_types") = std::vector{}, py::arg("format") = "fasta", - py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, - py::arg("gap_open") = -1.0f, - py::arg("gap_extend") = -1.0f, - py::arg("terminal_gap_extend") = -1.0f, - py::arg("n_threads") = 1, + py::arg("seq_type") = KALIGN_TYPE_PROTEIN, + py::arg("seed") = (uint64_t)42, + py::arg("min_support") = 0, py::arg("refine") = KALIGN_REFINE_NONE, - py::arg("adaptive_budget") = 0, - py::arg("ensemble") = 0, - py::arg("ensemble_seed") = (uint64_t)42, - py::arg("dist_scale") = 0.0f, py::arg("vsm_amax") = -1.0f, - py::arg("min_support") = 0, py::arg("realign") = 0, - py::arg("save_poar") = "", - py::arg("load_poar") = "", py::arg("seq_weights") = -1.0f, + py::arg("n_threads") = 1, py::arg("consistency_anchors") = 0, py::arg("consistency_weight") = 2.0f, + py::arg("run_vsm_amax") = std::vector{}, + py::arg("run_seq_weights") = std::vector{}, + py::arg("run_refine") = std::vector{}, + py::arg("run_realign") = std::vector{}, + py::arg("run_consistency_anchors") = std::vector{}, + py::arg("run_consistency_weight") = std::vector{}, + py::arg("consistency_merge") = 0, + py::arg("consistency_merge_weight") = 2.0f, R"pbdoc( - Align sequences from input file and write to output file. + Ensemble alignment with per-run parameters. - Unlike align_from_file, this preserves all sequence metadata - (names, descriptions) which is required for MSA comparison. + Each run gets its own gap penalties, matrix type, and tree noise. + Additional parameters can optionally be varied per-run by passing + arrays of the same length as run_gpo. When empty (default), the + shared scalar value is used for all runs. Parameters ---------- @@ -709,21 +718,154 @@ PYBIND11_MODULE(_core, m) { Path to input sequence file output_file : str Path to output alignment file - format : str, optional - Output format: "fasta", "msf", "clu" (default: "fasta") - seq_type : int, optional - Sequence type (default: auto-detect) - gap_open : float, optional - Gap opening penalty - gap_extend : float, optional - Gap extension penalty - terminal_gap_extend : float, optional - Terminal gap extension penalty - n_threads : int, optional - Number of threads (default: 1) + run_gpo : list of float + Per-run gap open penalties (length = n_runs) + run_gpe : list of float + Per-run gap extend penalties + run_tgpe : list of float + Per-run terminal gap extend penalties + run_noise : list of float + Per-run tree noise sigma values + run_types : list of int, optional + Per-run matrix types. Empty = use seq_type for all runs. + run_vsm_amax : list of float, optional + Per-run VSM amplitude. Empty = use vsm_amax for all runs. + run_seq_weights : list of float, optional + Per-run profile rebalancing weight. Empty = use seq_weights for all. + run_refine : list of int, optional + Per-run refinement mode (REFINE_* constants). Empty = use refine for all. + run_realign : list of int, optional + Per-run realign iterations. Empty = use realign for all. + run_consistency_anchors : list of int, optional + Per-run consistency rounds. Empty = use consistency_anchors for all. + run_consistency_weight : list of float, optional + Per-run consistency weight. Empty = use consistency_weight for all. )pbdoc"); - // Constants for sequence types + // In-memory alignment using a named mode preset + m.def("align", &align_mode, + py::arg("sequences"), + py::arg("mode"), + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + py::arg("n_threads") = 1, + "Align sequences using a named mode preset (fast/default/recall/accurate)."); + m.def("align_mode", &align_mode, + py::arg("sequences"), + py::arg("mode"), + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + py::arg("n_threads") = 1, + "Alias for align(). Align sequences using a named mode preset."); + + // File alignment returning (names, sequences) using a named mode preset + m.def("align_from_file", &align_from_file_mode, + py::arg("input_file"), + py::arg("mode"), + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + py::arg("n_threads") = 1, + "Align from file using a named mode preset. Returns (names, sequences) tuple."); + m.def("align_from_file_mode", &align_from_file_mode, + py::arg("input_file"), + py::arg("mode"), + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + py::arg("n_threads") = 1, + "Alias for align_from_file(). Align from file using a named mode preset."); + + // File-to-file alignment using a named mode preset + m.def("align_file_to_file", &align_file_to_file_mode, + py::arg("input_file"), + py::arg("output_file"), + py::arg("mode"), + py::arg("format") = "fasta", + py::arg("n_threads") = 1, + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + "Align file to file using a named mode preset (fast/default/recall/accurate)."); + m.def("align_file_to_file_mode", &align_file_to_file_mode, + py::arg("input_file"), + py::arg("output_file"), + py::arg("mode"), + py::arg("format") = "fasta", + py::arg("n_threads") = 1, + py::arg("seq_type") = KALIGN_TYPE_UNDEFINED, + py::arg("gap_open") = -1.0f, + py::arg("gap_extend") = -1.0f, + py::arg("terminal_gap_extend") = -1.0f, + "Alias for align_file_to_file(). Align file to file using a named mode preset."); + + m.def("add_to_alignment_file", []( + const std::string& existing_file, + const std::string& new_seqs_file, + const std::string& output_file, + const std::string& format, + int n_threads + ) { + struct msa* existing = nullptr; + struct msa* new_seqs = nullptr; + + int result = kalign_read_input( + const_cast(existing_file.c_str()), &existing, 1); + if (result != 0 || !existing) { + throw std::runtime_error("Failed to read existing alignment: " + existing_file); + } + + result = kalign_read_sequences( + const_cast(new_seqs_file.c_str()), &new_seqs, 1); + if (result != 0 || !new_seqs) { + kalign_free_msa(existing); + throw std::runtime_error("Failed to read new sequences: " + new_seqs_file); + } + + result = kalign_add_sequences(existing, new_seqs, n_threads); + if (result != 0) { + kalign_free_msa(existing); + kalign_free_msa(new_seqs); + throw std::runtime_error("Failed to add sequences to alignment"); + } + + kalign_free_msa(new_seqs); + + result = kalign_write_msa(existing, + const_cast(output_file.c_str()), + const_cast(format.c_str())); + kalign_free_msa(existing); + if (result != 0) { + throw std::runtime_error("Failed to write output alignment"); + } + }, + py::arg("existing_file"), + py::arg("new_seqs_file"), + py::arg("output_file"), + py::arg("format") = "fasta", + py::arg("n_threads") = 1, + "Add new sequences to an existing alignment without re-aligning existing sequences."); + + // Matrix constants (canonical names) + m.attr("MATRIX_AUTO") = KALIGN_MATRIX_AUTO; + m.attr("MATRIX_PFASUM43") = KALIGN_MATRIX_PFASUM43; + m.attr("MATRIX_PFASUM60") = KALIGN_MATRIX_PFASUM60; + m.attr("MATRIX_CORBLOSUM66") = KALIGN_MATRIX_CORBLOSUM66; + m.attr("MATRIX_DNA") = KALIGN_MATRIX_DNA; + m.attr("MATRIX_DNA_INTERNAL") = KALIGN_MATRIX_DNA_INTERNAL; + m.attr("MATRIX_RNA") = KALIGN_MATRIX_RNA; + m.attr("MATRIX_NUC_1PAM") = KALIGN_MATRIX_NUC_1PAM; + m.attr("MATRIX_NUC_20PAM") = KALIGN_MATRIX_NUC_20PAM; + m.attr("MATRIX_NUC_200PAM") = KALIGN_MATRIX_NUC_200PAM; + + // Backward compat: old names point to new values m.attr("DNA") = KALIGN_TYPE_DNA; m.attr("DNA_INTERNAL") = KALIGN_TYPE_DNA_INTERNAL; m.attr("RNA") = KALIGN_TYPE_RNA; @@ -732,6 +874,7 @@ PYBIND11_MODULE(_core, m) { m.attr("PROTEIN_PFASUM60") = KALIGN_TYPE_PROTEIN_PFASUM60; m.attr("PROTEIN_PFASUM_AUTO") = KALIGN_TYPE_PROTEIN_PFASUM_AUTO; m.attr("PROTEIN_DIVERGENT") = KALIGN_TYPE_PROTEIN_DIVERGENT; + m.attr("PROTEIN_CORBLOSUM66") = KALIGN_TYPE_PROTEIN_CORBLOSUM66; m.attr("AUTO") = KALIGN_TYPE_UNDEFINED; // Constants for refinement modes @@ -739,4 +882,5 @@ PYBIND11_MODULE(_core, m) { m.attr("REFINE_ALL") = KALIGN_REFINE_ALL; m.attr("REFINE_CONFIDENT") = KALIGN_REFINE_CONFIDENT; m.attr("REFINE_INLINE") = KALIGN_REFINE_INLINE; + } \ No newline at end of file diff --git a/python-kalign/cli.py b/python-kalign/cli.py index bb7a848..010dc41 100644 --- a/python-kalign/cli.py +++ b/python-kalign/cli.py @@ -19,7 +19,6 @@ def _resolve_version() -> str: - # kalign-test is the name of the test distribution, which may be installed in test environments. If it's present, use its version; otherwise, fall back to the main kalign distribution. If neither is found, try to import __version__ from the package, and if that fails, return "unknown". for dist_name in ("kalign-python",): try: return dist_version(dist_name) @@ -63,23 +62,29 @@ def _build_parser() -> argparse.ArgumentParser: dest="seq_type", help="Sequence type: auto, dna, rna, internal, protein, divergent (default: auto).", ) + parser.add_argument( + "--mode", + default="default", + choices=["fast", "default", "recall", "accurate"], + help="Alignment mode preset (default: default).", + ) parser.add_argument( "--gpo", type=float, default=None, - help="Gap open penalty (default: Kalign internal defaults).", + help="Gap open penalty (overrides mode preset).", ) parser.add_argument( "--gpe", type=float, default=None, - help="Gap extension penalty (default: Kalign internal defaults).", + help="Gap extension penalty (overrides mode preset).", ) parser.add_argument( "--tgpe", type=float, default=None, - help="Terminal gap extension penalty (default: Kalign internal defaults).", + help="Terminal gap extension penalty (overrides mode preset).", ) parser.add_argument( "-n", @@ -88,69 +93,6 @@ def _build_parser() -> argparse.ArgumentParser: default=1, help="Number of threads to use (default: 1).", ) - parser.add_argument( - "--refine", - default="confident", - help="Refinement mode: none, all, confident (default: confident).", - ) - parser.add_argument( - "--adaptive-budget", - action="store_true", - default=False, - help="Scale refinement trial count by uncertainty.", - ) - ens = parser.add_argument_group( - "ensemble options", - "These options only take effect when --ensemble is used.", - ) - ens.add_argument( - "--ensemble", - type=int, - default=0, - help="Number of ensemble runs (default: 0 = off). Try 3-5 for better accuracy.", - ) - ens.add_argument( - "--ensemble-seed", - type=int, - default=42, - help="RNG seed for ensemble (default: 42).", - ) - ens.add_argument( - "--min-support", - type=int, - default=0, - help="Explicit consensus threshold (default: 0 = auto).", - ) - ens.add_argument( - "--save-poar", - default=None, - help="Save POAR consensus table to file for later re-thresholding.", - ) - ens.add_argument( - "--load-poar", - default=None, - help="Load POAR consensus table from file (skip alignment, just re-threshold).", - ) - - adv = parser.add_argument_group("advanced options") - adv.add_argument( - "--dist-scale", - type=float, - default=0.0, - help="Distance scaling parameter (default: 0.0).", - ) - adv.add_argument( - "--vsm-amax", - type=float, - default=None, - help="Variable Scoring Matrix a_max (default: Kalign internal defaults).", - ) - adv.add_argument( - "--realign", - type=int, - default=0, - help="Realignment iterations (default: 0 = off).", - ) parser.add_argument( "-V", @@ -204,30 +146,16 @@ def main(argv: Optional[list[str]] = None) -> int: input_path = str(tmp_path) try: - kwargs = dict( + result = kalign.align_from_file( + input_path, seq_type=args.seq_type, gap_open=args.gpo, gap_extend=args.gpe, terminal_gap_extend=args.tgpe, n_threads=args.nthreads, - refine=args.refine, - adaptive_budget=args.adaptive_budget, - ensemble=args.ensemble, - ensemble_seed=args.ensemble_seed, - min_support=args.min_support, - realign=args.realign, - dist_scale=args.dist_scale, + mode=args.mode, ) - if args.vsm_amax is not None: - kwargs["vsm_amax"] = args.vsm_amax - if args.save_poar is not None: - kwargs["save_poar"] = args.save_poar - if args.load_poar is not None: - kwargs["load_poar"] = args.load_poar - - result = kalign.align_from_file(input_path, **kwargs) - # Pass confidence data to Stockholm writer when available write_kwargs = dict( format=args.format, ids=result.names, diff --git a/python-kalign/io.py b/python-kalign/io.py index d929233..9409852 100644 --- a/python-kalign/io.py +++ b/python-kalign/io.py @@ -5,7 +5,6 @@ and writing alignments in various formats, with optional Biopython integration. """ -import os from pathlib import Path from typing import List, Optional, TextIO, Tuple, Union diff --git a/python-kalign/utils.py b/python-kalign/utils.py index c432d88..6eecd1b 100644 --- a/python-kalign/utils.py +++ b/python-kalign/utils.py @@ -6,7 +6,7 @@ """ from collections import Counter -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional import numpy as np diff --git a/scripts/bench_accuracy.py b/scripts/bench_accuracy.py new file mode 100644 index 0000000..ae035e8 --- /dev/null +++ b/scripts/bench_accuracy.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Alignment accuracy benchmark — reuses scoring from the manuscript repo. + +Discovers BAliBASE cases, runs kalign fast/default/recall/accurate, +scores with XML core block masks (same as manuscript pipeline). + +Usage: + uv run python scripts/bench_accuracy.py + uv run python scripts/bench_accuracy.py --modes fast,accurate +""" +import argparse +import json +import logging +import sys +import tempfile +import xml.etree.ElementTree as ET +from collections import defaultdict +from pathlib import Path + +import kalign + +logging.basicConfig(level=logging.INFO, format="%(message)s") +logger = logging.getLogger(__name__) + +BB_ROOT = Path(__file__).parent.parent / "benchmarks" / "data" / "downloads" / "bb3_release" + + +def parse_balibase_xml(xml_path): + """Parse BAliBASE XML to get core block column mask (from manuscript scoring.py).""" + tree = ET.parse(xml_path) + root = tree.getroot() + colsco = root.find(".//column-score/colsco-data") + if colsco is None or colsco.text is None: + return None + values = [int(v) for v in colsco.text.split()] + return [1 if v == 1 else 0 for v in values] + + +def discover_cases(): + """Find all BAliBASE cases (excluding BBS supplement).""" + cases = [] + for rv_dir in sorted(BB_ROOT.iterdir()): + if not rv_dir.is_dir() or not rv_dir.name.startswith("RV"): + continue + for tfa in sorted(rv_dir.glob("*.tfa")): + if tfa.stem.startswith("BBS"): + continue + msf = tfa.with_suffix(".msf") + xml = tfa.with_suffix(".xml") + if msf.exists(): + cases.append({ + "family": tfa.stem, + "category": rv_dir.name, + "unaligned": str(tfa), + "reference": str(msf), + "xml": str(xml) if xml.exists() else None, + }) + return cases + + +def score_case(ref_path, test_path, xml_path): + """Score one alignment using XML core blocks if available.""" + if xml_path and Path(xml_path).exists(): + mask = parse_balibase_xml(xml_path) + if mask: + return kalign.compare_detailed(ref_path, test_path, column_mask=mask) + return kalign.compare_detailed(ref_path, test_path, max_gap_frac=0.2) + + +def main(): + parser = argparse.ArgumentParser(description="BAliBASE accuracy benchmark") + parser.add_argument("--modes", default="fast,default,recall,accurate") + parser.add_argument("--threads", type=int, default=1) + parser.add_argument("--output", help="Save results JSON") + args = parser.parse_args() + + if not BB_ROOT.exists(): + logger.error("BAliBASE not found at %s", BB_ROOT) + return 1 + + modes = args.modes.split(",") + cases = discover_cases() + logger.info("%d BAliBASE cases, modes: %s", len(cases), modes) + + results = [] + for mode in modes: + with tempfile.TemporaryDirectory() as tmpdir: + for i, case in enumerate(cases): + out = str(Path(tmpdir) / f"{case['family']}.fa") + kalign.align_file_to_file( + case["unaligned"], out, + mode=mode, n_threads=args.threads, + ) + score = score_case(case["reference"], out, case["xml"]) + results.append({ + "family": case["family"], + "category": case["category"], + "mode": mode, + **score, + }) + if (i + 1) % 50 == 0: + logger.info(" %s: %d/%d", mode, i + 1, len(cases)) + logger.info(" %s: done", mode) + + # Aggregate and print + print() + print(f"{'Mode':12s} {'Recall':>8s} {'Prec':>8s} {'F1':>8s} {'TC':>8s} (n)") + print("=" * 52) + for mode in modes: + mc = [r for r in results if r["mode"] == mode] + n = len(mc) + r = sum(c["recall"] for c in mc) / n + p = sum(c["precision"] for c in mc) / n + f = sum(c["f1"] for c in mc) / n + t = sum(c["tc"] for c in mc) / n + print(f"{mode:12s} {r:8.3f} {p:8.3f} {f:8.3f} {t:8.3f} ({n})") + + if args.output: + with open(args.output, "w") as f: + json.dump(results, f, indent=2) + logger.info("Saved to %s", args.output) + + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/scripts/bench_confidence_filtering.py b/scripts/bench_confidence_filtering.py new file mode 100644 index 0000000..ee97057 --- /dev/null +++ b/scripts/bench_confidence_filtering.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""Benchmark: SP/TC on confident columns only. + +Scores BAliBASE alignments using only columns above a confidence threshold. +Shows that kalign's confidence scores identify reliable columns, and that +SP/TC on those columns is dramatically higher than on all columns. + +Usage: + uv run python scripts/bench_confidence_filtering.py +""" +import json +import os +import sys +import tempfile +import xml.etree.ElementTree as ET +from pathlib import Path + +import kalign + +BB_ROOT = Path(__file__).parent.parent / "benchmarks" / "data" / "downloads" / "bb3_release" + + +def parse_balibase_xml(xml_path): + tree = ET.parse(xml_path) + root = tree.getroot() + colsco = root.find(".//column-score/colsco-data") + if colsco is None or colsco.text is None: + return None + values = [int(v) for v in colsco.text.split()] + return [1 if v == 1 else 0 for v in values] + + +def discover_cases(): + cases = [] + for rv_dir in sorted(BB_ROOT.iterdir()): + if not rv_dir.is_dir() or not rv_dir.name.startswith("RV"): + continue + for tfa in sorted(rv_dir.glob("*.tfa")): + if tfa.stem.startswith("BBS"): + continue + msf = tfa.with_suffix(".msf") + xml = tfa.with_suffix(".xml") + if msf.exists(): + cases.append({ + "family": tfa.stem, + "category": rv_dir.name, + "unaligned": str(tfa), + "reference": str(msf), + "xml": str(xml) if xml.exists() else None, + }) + return cases + + +def score_with_xml(ref_path, test_path, xml_path): + """Score alignment using XML core block mask (standard BAliBASE scoring).""" + if xml_path and Path(xml_path).exists(): + mask = parse_balibase_xml(xml_path) + if mask: + return kalign.compare_detailed(ref_path, test_path, column_mask=mask) + return kalign.compare_detailed(ref_path, test_path, max_gap_frac=0.2) + + +def score_filtered(ref_path, test_sequences, test_names, conf, threshold, xml_path, tmpdir, family): + """Score only confident columns: mask out low-confidence residues, then score.""" + # Write a filtered test alignment: low-confidence residues become gaps + filtered_out = os.path.join(tmpdir, f"{family}_filt.fa") + with open(filtered_out, 'w') as f: + for name, seq in zip(test_names, test_sequences): + filtered = [] + for col in range(len(seq)): + if col < len(conf) and conf[col] >= threshold: + filtered.append(seq[col]) + else: + filtered.append('-') + f.write(f">{name}\n{''.join(filtered)}\n") + + # Score with XML mask (normal BAliBASE scoring on the filtered alignment) + return score_with_xml(ref_path, filtered_out, xml_path) + + +def main(): + if not BB_ROOT.exists(): + print(f"ERROR: BAliBASE not found at {BB_ROOT}") + return 1 + + cases = discover_cases() + thresholds = [0.0, 0.3, 0.5, 0.7, 0.9] + + print(f"{len(cases)} BAliBASE cases, accurate mode") + print() + + results_by_thresh = {t: {"recall": [], "precision": [], "f1": [], "tc": [], "n_cols": [], "n_total": []} + for t in thresholds} + + with tempfile.TemporaryDirectory() as tmpdir: + for i, case in enumerate(cases): + # Use align_from_file to get BOTH alignment and confidence in one call + aln = kalign.align_from_file(case["unaligned"], mode="accurate") + conf = aln.column_confidence + + # Write the alignment to file for scoring + out = os.path.join(tmpdir, f"{case['family']}.fa") + with open(out, 'w') as f: + for name, seq in zip(aln.names, aln.sequences): + f.write(f">{name}\n{seq}\n") + + for thresh in thresholds: + if thresh == 0.0: + # No confidence filtering — standard XML scoring + score = score_with_xml(case["reference"], out, case["xml"]) + else: + if conf is None: + continue + # Mask low-confidence residues to gaps, then score normally + score = score_filtered( + case["reference"], aln.sequences, aln.names, + conf, thresh, case["xml"], tmpdir, case["family"] + ) + + if score is not None: + for k in ["recall", "precision", "f1", "tc"]: + results_by_thresh[thresh][k].append(score[k]) + if conf is not None: + n_confident = sum(1 for c in conf if c >= thresh) + results_by_thresh[thresh]["n_cols"].append(n_confident) + results_by_thresh[thresh]["n_total"].append(len(conf)) + + if (i + 1) % 50 == 0: + print(f" {i+1}/{len(cases)}...") + + # Print results + print() + print(f"{'Threshold':>10s} {'Recall':>8s} {'Prec':>8s} {'F1':>8s} {'TC':>8s} {'Cols%':>7s} (n)") + print("=" * 60) + for thresh in thresholds: + r = results_by_thresh[thresh] + n = len(r["recall"]) + if n == 0: + continue + avg_r = sum(r["recall"]) / n + avg_p = sum(r["precision"]) / n + avg_f = sum(r["f1"]) / n + avg_t = sum(r["tc"]) / n + + if r["n_cols"] and r["n_total"]: + avg_pct = sum(c / t * 100 for c, t in zip(r["n_cols"], r["n_total"])) / len(r["n_cols"]) + else: + avg_pct = 100.0 + + label = "all" if thresh == 0.0 else f">={thresh}" + print(f"{label:>10s} {avg_r:8.3f} {avg_p:8.3f} {avg_f:8.3f} {avg_t:8.3f} {avg_pct:6.1f}% ({n})") + + # Also print comparison to other tools on all columns + print() + print("Reference (all columns, from manuscript):") + print(f"{'mafft':>10s} {'0.867':>8s} {'0.715':>8s} {'0.778':>8s} {'0.590':>8s}") + print(f"{'muscle':>10s} {'0.870':>8s} {'0.721':>8s} {'0.783':>8s} {'0.581':>8s}") + print(f"{'clustalo':>10s} {'0.840':>8s} {'0.710':>8s} {'0.764':>8s} {'0.559':>8s}") + + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/scripts/verify_balibase.py b/scripts/verify_balibase.py new file mode 100644 index 0000000..305e325 --- /dev/null +++ b/scripts/verify_balibase.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +"""BAliBASE verification: generate checksums or compare against baseline. + +Usage: + # Generate baseline checksums (run with CURRENT code, 1 thread) + uv run python scripts/verify_balibase.py baseline --output baseline_checksums.json + + # Compare against baseline (run AFTER code change + rebuild) + uv run python scripts/verify_balibase.py compare --baseline baseline_checksums.json + + # Timing benchmark (20 largest cases, multiple thread counts) + uv run python scripts/verify_balibase.py timing --threads 1,4,8,16 + + # Quick test (10 cases only) + uv run python scripts/verify_balibase.py baseline --output baseline.json --max-cases 10 +""" + +import argparse +import hashlib +import json +import os +import sys +import tempfile +import time +from pathlib import Path + +import kalign + + +BB_ROOT = Path(__file__).parent.parent / "benchmarks" / "data" / "downloads" / "bb3_release" +MODES = ["fast", "default", "accurate"] + + +def get_cases(max_cases=0): + """Discover all BAliBASE .tfa files, sorted by size (largest first for timing).""" + cases = [] + for rv_dir in sorted(BB_ROOT.iterdir()): + if not rv_dir.is_dir() or not rv_dir.name.startswith("RV"): + continue + for tfa in sorted(rv_dir.glob("*.tfa")): + msf = tfa.with_suffix(".msf") + if msf.exists(): + cases.append({ + "name": tfa.stem, + "category": rv_dir.name, + "unaligned": str(tfa), + "reference": str(msf), + "size": tfa.stat().st_size, + }) + # Sort by size descending (largest first — useful for timing) + cases.sort(key=lambda c: -c["size"]) + if max_cases > 0: + cases = cases[:max_cases] + return cases + + +def sha256_file(path): + """Compute SHA256 of a file.""" + h = hashlib.sha256() + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + h.update(chunk) + return h.hexdigest() + + +def run_alignment(case, mode, n_threads, output_path): + """Run one alignment, return (sha256, elapsed_seconds).""" + t0 = time.perf_counter() + kalign.align_file_to_file( + case["unaligned"], output_path, + mode=mode, n_threads=n_threads, format="fasta", + ) + elapsed = time.perf_counter() - t0 + checksum = sha256_file(output_path) + return checksum, elapsed + + +def cmd_baseline(args): + """Generate baseline checksums for all cases × all modes.""" + cases = get_cases(args.max_cases) + modes = args.modes.split(",") if args.modes else MODES + n_threads = 1 # baseline always single-threaded for determinism + + print(f"Generating baseline: {len(cases)} cases × {len(modes)} modes, {n_threads} thread") + print(f"BAliBASE root: {BB_ROOT}") + + results = {} + total = len(cases) * len(modes) + done = 0 + + with tempfile.TemporaryDirectory() as tmpdir: + for case in cases: + for mode in modes: + out_path = os.path.join(tmpdir, f"{case['name']}_{mode}.fa") + checksum, elapsed = run_alignment(case, mode, n_threads, out_path) + + key = f"{case['name']}:{mode}" + results[key] = { + "checksum": checksum, + "time": round(elapsed, 3), + "category": case["category"], + } + + done += 1 + if args.verbose: + print(f" [{done}/{total}] {key}: {checksum[:12]}... ({elapsed:.2f}s)") + elif done % 50 == 0: + print(f" [{done}/{total}]...") + + # Save + output = { + "n_cases": len(cases), + "modes": modes, + "n_threads": n_threads, + "checksums": results, + } + with open(args.output, "w") as f: + json.dump(output, f, indent=2) + + print(f"\nBaseline saved to {args.output}: {len(results)} checksums") + + +def cmd_compare(args): + """Compare current code output against baseline checksums.""" + with open(args.baseline) as f: + baseline = json.load(f) + + cases_by_name = {c["name"]: c for c in get_cases()} + modes = baseline["modes"] + n_threads = args.threads # can test with >1 thread + + checksums = baseline["checksums"] + print(f"Comparing against baseline: {len(checksums)} checksums, {n_threads} threads") + + mismatches = [] + matches = 0 + + with tempfile.TemporaryDirectory() as tmpdir: + total = len(checksums) + done = 0 + + for key, bdata in checksums.items(): + name, mode = key.split(":") + if name not in cases_by_name: + print(f" SKIP {key}: case not found") + continue + + case = cases_by_name[name] + out_path = os.path.join(tmpdir, f"{name}_{mode}.fa") + checksum, elapsed = run_alignment(case, mode, n_threads, out_path) + + done += 1 + if checksum == bdata["checksum"]: + matches += 1 + if args.verbose: + print(f" [{done}/{total}] {key}: OK ({elapsed:.2f}s)") + else: + mismatches.append({ + "key": key, + "baseline": bdata["checksum"], + "current": checksum, + }) + print(f" [{done}/{total}] {key}: MISMATCH!") + print(f" baseline: {bdata['checksum'][:16]}...") + print(f" current: {checksum[:16]}...") + + if not args.verbose and done % 50 == 0: + print(f" [{done}/{total}] ({matches} match, {len(mismatches)} mismatch)...") + + print(f"\n{'='*60}") + print(f"Results: {matches} identical, {len(mismatches)} mismatched (of {done})") + + if mismatches: + print(f"\nMISMATCHES:") + for m in mismatches: + print(f" {m['key']}") + return 1 + else: + print("ALL IDENTICAL — verification passed.") + return 0 + + +def cmd_timing(args): + """Timing benchmark on largest cases.""" + n_cases = args.max_cases or 20 + cases = get_cases(n_cases) + modes = args.modes.split(",") if args.modes else MODES + thread_counts = [int(x) for x in args.threads.split(",")] + + print(f"Timing benchmark: {len(cases)} cases × {len(modes)} modes × {len(thread_counts)} thread configs") + + results = {} + + with tempfile.TemporaryDirectory() as tmpdir: + for mode in modes: + print(f"\n=== Mode: {mode} ===") + for n_threads in thread_counts: + total_time = 0.0 + for case in cases: + out_path = os.path.join(tmpdir, f"{case['name']}_{mode}_{n_threads}.fa") + _, elapsed = run_alignment(case, mode, n_threads, out_path) + total_time += elapsed + + key = f"{mode}:{n_threads}" + results[key] = round(total_time, 2) + + baseline_key = f"{mode}:{thread_counts[0]}" + baseline_time = results.get(baseline_key, total_time) + speedup = baseline_time / total_time if total_time > 0 else 0 + + print(f" {n_threads:2d} threads: {total_time:7.1f}s total (speedup: {speedup:.2f}x)") + + # Print summary table + print(f"\n{'='*60}") + print(f"{'Mode':<12s}", end="") + for t in thread_counts: + print(f" {t:>2d}T", end="") + print(" speedup") + print("-" * (12 + 5 * len(thread_counts) + 10)) + for mode in modes: + print(f"{mode:<12s}", end="") + baseline = results.get(f"{mode}:{thread_counts[0]}", 1) + for t in thread_counts: + val = results.get(f"{mode}:{t}", 0) + print(f" {val:>4.0f}", end="") + last = results.get(f"{mode}:{thread_counts[-1]}", 1) + print(f" {baseline/last:.2f}x") + + +def main(): + parser = argparse.ArgumentParser(description="BAliBASE verification and timing") + sub = parser.add_subparsers(dest="command", required=True) + + p_base = sub.add_parser("baseline", help="Generate baseline checksums") + p_base.add_argument("--output", required=True, help="Output JSON file") + p_base.add_argument("--modes", default=None, help="Comma-separated modes (default: fast,default,accurate)") + p_base.add_argument("--max-cases", type=int, default=0, help="Limit number of cases (0=all)") + p_base.add_argument("-v", "--verbose", action="store_true") + + p_cmp = sub.add_parser("compare", help="Compare against baseline") + p_cmp.add_argument("--baseline", required=True, help="Baseline JSON file") + p_cmp.add_argument("--threads", type=int, default=1, help="Thread count for comparison run") + p_cmp.add_argument("-v", "--verbose", action="store_true") + + p_time = sub.add_parser("timing", help="Timing benchmark") + p_time.add_argument("--threads", default="1,4,8,16", help="Comma-separated thread counts") + p_time.add_argument("--modes", default=None, help="Comma-separated modes") + p_time.add_argument("--max-cases", type=int, default=20, help="Number of largest cases") + + args = parser.parse_args() + + if not BB_ROOT.exists(): + print(f"ERROR: BAliBASE not found at {BB_ROOT}") + print("Run: uv run python -m benchmarks --download-only --dataset balibase") + return 1 + + if args.command == "baseline": + return cmd_baseline(args) + elif args.command == "compare": + return cmd_compare(args) + elif args.command == "timing": + return cmd_timing(args) + + +if __name__ == "__main__": + sys.exit(main() or 0) diff --git a/setup_server.sh b/setup_server.sh new file mode 100755 index 0000000..f8ea3f2 --- /dev/null +++ b/setup_server.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Setup script for running kalign parameter optimization on a Linux server. +# Usage: bash setup_server.sh +set -euo pipefail + +echo "=== Kalign optimization server setup ===" + +# Check basics +echo "Checking prerequisites..." +command -v cmake >/dev/null 2>&1 || { echo "ERROR: cmake not found. Install with: sudo apt install cmake (or module load cmake)"; exit 1; } +command -v gcc >/dev/null 2>&1 || command -v cc >/dev/null 2>&1 || { echo "ERROR: C compiler not found."; exit 1; } + +# Install uv if not present +if ! command -v uv >/dev/null 2>&1; then + echo "Installing uv..." + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" +fi + +echo "uv version: $(uv --version)" + +# Build C library + Python extension +echo "" +echo "=== Building kalign Python package ===" +uv pip install -e ".[dev]" 2>/dev/null || uv pip install -e . + +# Install optimizer dependencies +echo "" +echo "=== Installing optimizer dependencies ===" +uv pip install pymoo rich + +# Quick smoke test +echo "" +echo "=== Smoke test ===" +uv run python -c " +import kalign +print(f'kalign version: {kalign.__version__}') +result = kalign.align(['ACDEFGHIK', 'ACDGHIK', 'ACDEFHIK']) +print(f'Alignment test: OK ({len(result)} sequences)') +" + +# Show system info +echo "" +echo "=== System info ===" +echo "CPU: $(nproc) threads available" +echo "RAM: $(free -h 2>/dev/null | awk '/^Mem:/{print $2}' || echo 'unknown')" +echo "Python: $(uv run python --version)" + +echo "" +echo "=== Ready! ===" +echo "" +echo "Example runs:" +echo "" +echo " # Quick test (10 minutes):" +echo " uv run python -m benchmarks.optimize_params --pop-size 20 --n-gen 5 --n-workers 8 --n-threads 1" +echo "" +echo " # Single-run optimization (~2-3 hours):" +echo " uv run python -m benchmarks.optimize_params --pop-size 60 --n-gen 50 --n-workers 56 --n-threads 1" +echo "" +echo " # Resume after interrupt:" +echo " uv run python -m benchmarks.optimize_params --resume benchmarks/results/optim/gen_checkpoint.pkl --n-gen 80 --n-workers 56 --n-threads 1" diff --git a/src/parameters.c b/src/parameters.c index 4242b9f..41171c4 100644 --- a/src/parameters.c +++ b/src/parameters.c @@ -16,7 +16,7 @@ static int get_default_thread_count(void) { int cores = 1; - + #ifdef HAVE_OPENMP cores = omp_get_num_procs(); #elif defined(_WIN32) @@ -27,11 +27,11 @@ static int get_default_thread_count(void) cores = sysconf(_SC_NPROCESSORS_ONLN); if (cores <= 0) cores = 1; #endif - + if (cores > 1) cores = cores - 1; if (cores > 16) cores = 16; if (cores < 1) cores = 1; - + return cores; } @@ -39,42 +39,32 @@ struct parameters*init_param(void) { struct parameters* param = NULL; MMALLOC(param, sizeof(struct parameters)); - param->dist_method = KALIGNDIST_BPM; - param->aln_param_file = NULL; - param->param_set = -1; param->infile = NULL; param->num_infiles = 0; param->input = NULL; param->outfile = NULL; param->format = NULL; - param->reformat = 0; - param->rename = 0; param->help_flag = 0; param->dump_internal = 0; - param->type = -1; - param->gpo = -1.0; param->gpe = -1.0; param->tgpe = -1.0; - param->matadd = 0.0F; - param->chaos = 0; param->nthreads = get_default_thread_count(); - param->clean = 0; - param->unalign = 0; - param->refine = KALIGN_REFINE_NONE; - param->adaptive_budget = 0; - param->ensemble = 0; - param->ensemble_seed = 42; param->min_support = 0; - param->save_poar = NULL; param->load_poar = NULL; - param->consistency_anchors = 5; - param->consistency_weight = 2.0f; - param->realign = 0; - param->vsm_amax = -1.0f; /* sentinel: use C defaults */ - param->mode = 0; /* 0=default, 1=fast, 2=precise */ + param->mode = NULL; param->quiet = 0; + param->out_format = 0; + param->reformat = 0; + param->rename = 0; + param->clean = 0; + param->unalign = 0; + param->add_file = NULL; + param->existing_file = NULL; + param->confidence_threshold = 0.0f; + param->confidence_style = 0; /* KALIGN_MASK_LOWERCASE */ + param->confidence_output = NULL; return param; ERROR: free_parameters(param); diff --git a/src/parameters.h b/src/parameters.h index 2e7843a..516ed94 100644 --- a/src/parameters.h +++ b/src/parameters.h @@ -14,47 +14,33 @@ #endif #endif - -#define KALIGNDIST_ALN 0 -#define KALIGNDIST_BPM 1 -#define KALIGNDIST_WU 2 - struct parameters{ char **infile; char *input; char *outfile; char* format; - char* aln_param_file; int type; float gpo; float gpe; float tgpe; - float matadd; - int chaos; - int out_format; - int param_set; - int dist_method; int num_infiles; - int reformat; - int rename; /* rename sequences - to make bali_score swallow the alignments */ - int dump_internal; + int out_format; int nthreads; - int clean; - int unalign; - int refine; - int adaptive_budget; - int ensemble; - uint64_t ensemble_seed; int min_support; - char* save_poar; char* load_poar; - int consistency_anchors; - float consistency_weight; - int realign; - float vsm_amax; - int mode; /* 0=default, 1=fast, 2=precise */ + char* mode; /* "fast", "default", "recall", "accurate" (NULL = default) */ + char* add_file; /* new sequences to add to existing alignment */ + char* existing_file; /* existing alignment to add sequences to */ + float confidence_threshold; /* mask columns below this confidence (0=off) */ + int confidence_style; /* KALIGN_MASK_LOWERCASE or KALIGN_MASK_REMOVE */ + char* confidence_output; /* write per-column confidence to file (NULL=off) */ int help_flag; int quiet; + int dump_internal; + int reformat; + int rename; + int clean; + int unalign; }; EXTERN struct parameters* init_param(void); diff --git a/src/run_kalign.c b/src/run_kalign.c index c25060d..918545b 100644 --- a/src/run_kalign.c +++ b/src/run_kalign.c @@ -1,7 +1,8 @@ #include "tldevel.h" #include "tlmisc.h" #include "kalign/kalign.h" -/* #include "version.h" */ +#include "msa_struct.h" +#include "aln_add.h" #include "parameters.h" #include @@ -9,34 +10,21 @@ #include #include - - - -#define OPT_SET 1 #define OPT_SHOWW 5 #define OPT_GPO 6 #define OPT_GPE 7 #define OPT_TGPE 8 - -#define OPT_NTHREADS 10 - #define OPT_ALN_TYPE 13 -#define OPT_REFINE 14 -#define OPT_ADAPTIVE_BUDGET 15 -#define OPT_ENSEMBLE 16 -#define OPT_ENSEMBLE_SEED 17 -#define OPT_MIN_SUPPORT 18 -#define OPT_SAVE_POAR 19 +#define OPT_MODE 22 #define OPT_LOAD_POAR 20 -#define OPT_CONSISTENCY 21 -#define OPT_FAST 22 -#define OPT_PRECISE 23 -#define OPT_REALIGN 24 -#define OPT_VSM_AMAX 25 -#define OPT_CONSISTENCY_WEIGHT 26 +#define OPT_MIN_SUPPORT 18 +#define OPT_CONF_THRESHOLD 30 +#define OPT_CONF_STYLE 31 +#define OPT_CONF_OUTPUT 32 +#define OPT_ADD 33 +#define OPT_EXISTING 34 -static int set_aln_type(char* in, int* type ); -static int set_refine_mode(char* in, int* refine); +static int set_aln_type(char* in, int* type); static int run_kalign(struct parameters* param); @@ -49,42 +37,35 @@ int print_kalign_help(char * argv[]) const char usage[] = " -i -o "; char* basename = NULL; - RUN(tlfilename(argv[0], &basename)); fprintf(stdout,"\nUsage: %s %s\n\n",basename ,usage); - fprintf(stdout,"Modes:\n\n"); - fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"(default)","Consistency anchors + VSM (best general-purpose)"); - fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"--fast","VSM only, no consistency (fastest)"); - fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"--precise","Ensemble(3) + VSM + realign (highest precision)"); + fprintf(stdout,"Modes (--mode ):\n\n"); + fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"fast","Single run, fastest"); + fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"default","Single run with consistency anchors (default)"); + fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"recall","Ensemble, optimized for recall"); + fprintf(stdout,"%*s%-*s: %s\n",3,"",MESSAGE_MARGIN-3,"accurate","Ensemble, highest precision"); fprintf(stdout,"\nOptions:\n\n"); fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--format","Output format." ,"[Fasta]" ); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--type","Alignment type (rna, dna, internal)." ,"[rna]" ); - fprintf(stdout,"%*s%-*s %s %s\n",3,"",MESSAGE_MARGIN-3,"","Options: protein, divergent (protein)" ,"" ); - fprintf(stdout,"%*s%-*s %s %s\n",3,"",MESSAGE_MARGIN-3,""," rna, dna, internal (nuc)." ,"" ); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--gpo","Gap open penalty." ,"[]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--gpe","Gap extension penalty." ,"[]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--tgpe","Terminal gap extension penalty." ,"[]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--refine","Refinement mode." ,"[none]"); - fprintf(stdout,"%*s%-*s %s %s\n",3,"",MESSAGE_MARGIN-3,"","Options: none, all, confident" ,"" ); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"-n/--nthreads","Number of threads." ,"[auto: N-1, max 16]"); - - fprintf(stdout,"\nEnsemble options:\n\n"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--ensemble","Number of ensemble runs." ,"[off; 5 if no value given]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--ensemble-seed","RNG seed for ensemble." ,"[42]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--min-support","Explicit consensus threshold." ,"[auto]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--save-poar","Save POAR table to file." ,"[off]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--type","Sequence type." ,"[auto]" ); + fprintf(stdout,"%*s%-*s %s %s\n",3,"",MESSAGE_MARGIN-3,"","Options: protein, dna, rna." ,"" ); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--gpo","Gap open penalty (overrides preset)." ,"[]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--gpe","Gap extension penalty (overrides preset)." ,"[]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--tgpe","Terminal gap extension penalty (overrides preset)." ,"[]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"-n/--nthreads","Number of threads." ,"[auto]"); fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--load-poar","Load POAR table for re-threshold." ,"[off]"); - fprintf(stdout,"\nAdvanced (usually managed by modes):\n\n"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--consistency","Anchor consistency (K anchors)." ,"[5]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--consistency-weight","Consistency anchor weight." ,"[2.0]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--realign","Alignment-guided tree rebuild iters." ,"[0]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--vsm-amax","VSM amplitude (0 to disable)." ,"[auto]"); - fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--adaptive-budget","Scale refinement trials by uncertainty." ,"[off]"); + fprintf(stdout,"\n"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--add FILE","New sequences to add to existing alignment." ,"[off]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--existing FILE","Existing alignment (new seqs added to this)." ,"[off]"); + + fprintf(stdout,"\n"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--confidence-threshold","Mask columns below this confidence (0-1)." ,"[off]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--confidence-style","Masking style: lowercase or remove." ,"[lowercase]"); + fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--confidence-output","Write per-column confidence to file." ,"[off]"); fprintf(stdout,"%*s%-*s: %s %s\n",3,"",MESSAGE_MARGIN-3,"--version (-V/-v)","Prints version." ,"[NA]" ); @@ -147,7 +128,6 @@ int main(int argc, char *argv[]) struct parameters* param = NULL; char* in = NULL; char* in_type = NULL; - char* in_refine = NULL; RUNP(param = init_param()); param->num_infiles = 0; @@ -155,26 +135,20 @@ int main(int argc, char *argv[]) while (1){ static struct option long_options[] ={ {"showw", 0,0,OPT_SHOWW }, - {"set", required_argument,0,OPT_SET}, {"format", required_argument, 0, 'f'}, {"type", required_argument, 0, OPT_ALN_TYPE}, {"gpo", required_argument, 0, OPT_GPO}, {"gpe", required_argument, 0, OPT_GPE}, {"tgpe", required_argument, 0, OPT_TGPE}, - {"refine", required_argument, 0, OPT_REFINE}, - {"adaptive-budget", no_argument, 0, OPT_ADAPTIVE_BUDGET}, - {"ensemble", optional_argument, 0, OPT_ENSEMBLE}, - {"ensemble-seed", required_argument, 0, OPT_ENSEMBLE_SEED}, - {"min-support", required_argument, 0, OPT_MIN_SUPPORT}, - {"save-poar", required_argument, 0, OPT_SAVE_POAR}, + {"mode", required_argument, 0, OPT_MODE}, {"load-poar", required_argument, 0, OPT_LOAD_POAR}, - {"consistency", required_argument, 0, OPT_CONSISTENCY}, - {"consistency-weight", required_argument, 0, OPT_CONSISTENCY_WEIGHT}, - {"fast", no_argument, 0, OPT_FAST}, - {"precise", no_argument, 0, OPT_PRECISE}, - {"realign", required_argument, 0, OPT_REALIGN}, - {"vsm-amax", required_argument, 0, OPT_VSM_AMAX}, + {"min-support", required_argument, 0, OPT_MIN_SUPPORT}, {"nthreads", required_argument, 0, 'n'}, + {"confidence-threshold", required_argument, 0, OPT_CONF_THRESHOLD}, + {"confidence-style", required_argument, 0, OPT_CONF_STYLE}, + {"confidence-output", required_argument, 0, OPT_CONF_OUTPUT}, + {"add", required_argument, 0, OPT_ADD}, + {"existing", required_argument, 0, OPT_EXISTING}, {"input", required_argument, 0, 'i'}, {"infile", required_argument, 0, 'i'}, {"in", required_argument, 0, 'i'}, @@ -191,7 +165,6 @@ int main(int argc, char *argv[]) c = getopt_long_only (argc, argv,"i:o:f:n:hqvV",long_options, &option_index); - /* Detect the end of the options. */ if (c == -1){ break; } @@ -202,9 +175,6 @@ int main(int argc, char *argv[]) case OPT_SHOWW: showw = 1; break; - case OPT_SET: - param->param_set = atoi(optarg); - break; case 'f': param->format = optarg; break; @@ -223,51 +193,33 @@ int main(int argc, char *argv[]) case OPT_TGPE: param->tgpe = atof(optarg); break; - case OPT_REFINE: - in_refine = optarg; - break; - case OPT_ADAPTIVE_BUDGET: - param->adaptive_budget = 1; - break; - case OPT_ENSEMBLE: - if(optarg){ - param->ensemble = atoi(optarg); - }else if(optind < argc && argv[optind][0] != '-'){ - param->ensemble = atoi(argv[optind]); - optind++; - }else{ - param->ensemble = 5; - } - break; - case OPT_ENSEMBLE_SEED: - param->ensemble_seed = (uint64_t)strtoull(optarg, NULL, 10); - break; - case OPT_MIN_SUPPORT: - param->min_support = atoi(optarg); - break; - case OPT_SAVE_POAR: - param->save_poar = optarg; + case OPT_MODE: + param->mode = optarg; break; case OPT_LOAD_POAR: param->load_poar = optarg; break; - case OPT_CONSISTENCY: - param->consistency_anchors = atoi(optarg); + case OPT_MIN_SUPPORT: + param->min_support = atoi(optarg); break; - case OPT_CONSISTENCY_WEIGHT: - param->consistency_weight = atof(optarg); + case OPT_CONF_THRESHOLD: + param->confidence_threshold = (float)atof(optarg); break; - case OPT_FAST: - param->mode = 1; + case OPT_CONF_STYLE: + if(strcmp(optarg, "remove") == 0){ + param->confidence_style = KALIGN_MASK_REMOVE; + }else{ + param->confidence_style = KALIGN_MASK_LOWERCASE; + } break; - case OPT_PRECISE: - param->mode = 2; + case OPT_CONF_OUTPUT: + param->confidence_output = optarg; break; - case OPT_REALIGN: - param->realign = atoi(optarg); + case OPT_ADD: + param->add_file = optarg; break; - case OPT_VSM_AMAX: - param->vsm_amax = atof(optarg); + case OPT_EXISTING: + param->existing_file = optarg; break; case 'h': param->help_flag = 1; @@ -324,8 +276,6 @@ int main(int argc, char *argv[]) param->num_infiles = 0; - /* Use "-" to indicate stdin (like samtools/bcftools). - * NULL in the infile array signals read_file_stdin() to use stdin. */ if(in){ param->num_infiles++; } @@ -334,71 +284,47 @@ int main(int argc, char *argv[]) param->num_infiles += argc-optind; } - if(param->num_infiles == 0){ + /* --add mode doesn't need -i input files */ + if(param->add_file != NULL && param->existing_file != NULL){ + /* Validate both files exist */ + param->num_infiles = 0; /* not using normal input */ + }else if(param->add_file != NULL || param->existing_file != NULL){ + LOG_MSG("Both --add and --existing must be specified together."); + free_parameters(param); + return EXIT_FAILURE; + }else if(param->num_infiles == 0){ RUN(print_kalign_help(argv)); LOG_MSG("No input files"); free_parameters(param); return EXIT_SUCCESS; } - MMALLOC(param->infile, sizeof(char*) * param->num_infiles); + if(param->num_infiles > 0){ + MMALLOC(param->infile, sizeof(char*) * param->num_infiles); - c = 0; - if(in){ - param->infile[c] = (strcmp(in, "-") == 0) ? NULL : in; - c++; - } + c = 0; + if(in){ + param->infile[c] = (strcmp(in, "-") == 0) ? NULL : in; + c++; + } - if (optind < argc){ - while (optind < argc){ - if(strcmp(argv[optind], "-") == 0){ - param->infile[c] = NULL; /* stdin */ - }else{ - param->infile[c] = argv[optind]; + if (optind < argc){ + while (optind < argc){ + if(strcmp(argv[optind], "-") == 0){ + param->infile[c] = NULL; /* stdin */ + }else{ + param->infile[c] = argv[optind]; + } + c++; + optind++; } - c++; - optind++; } } RUN(check_msa_format_string(param->format)); RUN(set_aln_type(in_type, ¶m->type)); - RUN(set_refine_mode(in_refine, ¶m->refine)); - - /* Apply mode presets. Explicit params (already set above) will have - * overridden their init_param() defaults, so we only fill in mode - * values for fields that are still at their init_param() defaults. */ - if(param->mode == 1){ - /* fast: no consistency anchors (unless user explicitly set --consistency) */ - if(param->consistency_anchors == 5){ /* still at default */ - param->consistency_anchors = 0; - } - }else if(param->mode == 2){ - /* precise: ensemble + realign */ - if(param->ensemble == 0){ - param->ensemble = 3; - } - if(param->realign == 0){ - param->realign = 1; - } - } - - /* if(param->chaos){ */ - /* if(param->chaos == 1){ */ - /* ERROR_MSG("Param chaos need to be bigger than 1 (currently %d)", param->chaos); */ - /* } */ - /* if(param->chaos > 10){ */ - /* ERROR_MSG("Param chaos bigger than 10 (currently %d)",param->chaos); */ - /* } */ - /* } */ RUN(run_kalign(param)); - /* if(devtest){ */ - /* for(c = 0; c < param->num_infiles;c++){ */ - /* MFREE(param->infile[c]); */ - /* } */ - /* } */ - free_parameters(param); return EXIT_SUCCESS; ERROR: @@ -409,60 +335,80 @@ int main(int argc, char *argv[]) int run_kalign(struct parameters* param) { struct msa* msa = NULL; + struct kalign_run_config runs[KALIGN_MAX_PRESET_RUNS]; + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + int n_runs = 0; + + /* --add mode: add new sequences to existing alignment */ + if(param->add_file != NULL && param->existing_file != NULL){ + struct msa* new_seqs = NULL; + + /* Read existing alignment */ + RUN(kalign_read_input(param->existing_file, &msa, param->quiet)); + + /* Read new sequences (allows single sequence) */ + RUN(kalign_read_sequences(param->add_file, &new_seqs, param->quiet)); + + if(!param->quiet){ + LOG_MSG("Adding %d sequences to existing alignment of %d sequences", + new_seqs->numseq, msa->numseq); + } + + /* Add new sequences to existing alignment */ + RUN(kalign_add_sequences(msa, new_seqs, param->nthreads)); + kalign_free_msa(new_seqs); + + /* Write result */ + RUN(kalign_write_msa(msa, param->outfile, param->format)); + kalign_free_msa(msa); + return OK; + } if(param->num_infiles == 1){ - RUN(kalign_read_input(param->infile[0], &msa,param->quiet)); + RUN(kalign_read_input(param->infile[0], &msa, param->quiet)); }else{ - for(int i = 0; i < param->num_infiles;i++){ - RUN(kalign_read_input(param->infile[i], &msa,param->quiet)); + for(int i = 0; i < param->num_infiles; i++){ + RUN(kalign_read_input(param->infile[i], &msa, param->quiet)); } } - - if(param->load_poar != NULL){ RUN(kalign_consensus_from_poar(msa, param->load_poar, param->min_support > 0 ? param->min_support : 2)); - }else if(param->ensemble > 0){ - RUN(kalign_ensemble(msa, - param->nthreads, - param->type, - param->ensemble, - param->gpo, - param->gpe, - param->tgpe, - param->ensemble_seed, - param->min_support, - param->save_poar, - param->refine, 0.0f, param->vsm_amax, - param->realign, -1.0f, - param->consistency_anchors, param->consistency_weight)); - }else if(param->realign > 0){ - RUN(kalign_run_realign(msa, - param->nthreads, - param->type, - param->gpo, - param->gpe, - param->tgpe, - param->refine, - param->adaptive_budget, - 0.0f, param->vsm_amax, - param->realign, -1.0f, - param->consistency_anchors, param->consistency_weight)); }else{ - RUN(kalign_run_seeded(msa, - param->nthreads, - param->type, - param->gpo, - param->gpe, - param->tgpe, - param->refine, - param->adaptive_budget, - 0, 0.0f, 0.0f, param->vsm_amax, -1.0f, - param->consistency_anchors, param->consistency_weight)); + /* Use mode preset (fast/default/recall/accurate). + kalign_get_mode_preset auto-selects protein vs nucleotide + based on the detected biotype. */ + const char* mode = param->mode ? param->mode : "default"; + int ret = kalign_get_mode_preset(mode, + kalign_msa_get_biotype(msa), + runs, &n_runs, &ens); + if(ret != 0){ + ERROR_MSG("Unknown mode: '%s'. Use: fast, default, recall, accurate.", mode); + } + + /* Override preset values with explicit user parameters. + Sentinel -1.0 means "use preset default". */ + for(int k = 0; k < n_runs; k++){ + if(param->gpo >= 0.0f) runs[k].gpo = param->gpo; + if(param->gpe >= 0.0f) runs[k].gpe = param->gpe; + if(param->tgpe >= 0.0f) runs[k].tgpe = param->tgpe; + } + + RUN(kalign_align_full(msa, runs, n_runs, &ens, param->nthreads)); } + /* Apply confidence masking if requested */ + if(param->confidence_threshold > 0.0f){ + RUN(kalign_mask_by_confidence(msa, param->confidence_threshold, + param->confidence_style)); + } + + /* Write per-column confidence scores if requested */ + if(param->confidence_output != NULL){ + RUN(kalign_write_confidence(msa, param->confidence_output)); + } RUN(kalign_write_msa(msa, param->outfile, param->format)); kalign_free_msa(msa); @@ -472,8 +418,7 @@ int run_kalign(struct parameters* param) return FAIL; } - -int set_aln_type(char* in, int* type ) +int set_aln_type(char* in, int* type) { int t = 0; if(in){ @@ -485,16 +430,8 @@ int set_aln_type(char* in, int* type ) t = KALIGN_TYPE_DNA_INTERNAL; }else if(strstr(in,"protein")){ t = KALIGN_TYPE_PROTEIN; - }else if(strstr(in,"divergent")){ - t = KALIGN_TYPE_PROTEIN_DIVERGENT; - }else if(strstr(in,"pfasum43")){ - t = KALIGN_TYPE_PROTEIN_PFASUM43; - }else if(strstr(in,"pfasum60")){ - t = KALIGN_TYPE_PROTEIN_PFASUM60; - }else if(strstr(in,"pfasum")){ - t = KALIGN_TYPE_PROTEIN_PFASUM_AUTO; }else{ - ERROR_MSG("In %s not recognized.",in); + ERROR_MSG("Sequence type '%s' not recognized. Use: protein, dna, rna.",in); } }else{ t = KALIGN_TYPE_UNDEFINED; @@ -504,22 +441,3 @@ int set_aln_type(char* in, int* type ) ERROR: return FAIL; } - -int set_refine_mode(char* in, int* refine) -{ - if(in){ - if(strstr(in,"all")){ - *refine = KALIGN_REFINE_ALL; - }else if(strstr(in,"confident")){ - *refine = KALIGN_REFINE_CONFIDENT; - }else if(strstr(in,"none")){ - *refine = KALIGN_REFINE_NONE; - }else{ - ERROR_MSG("Refine mode '%s' not recognized. Use: none, all, confident.", in); - } - } - /* When in is NULL, keep the default from init_param() */ - return OK; -ERROR: - return FAIL; -} diff --git a/tests/check-local.sh b/tests/check-local.sh new file mode 100755 index 0000000..1f281b9 --- /dev/null +++ b/tests/check-local.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# +# tests/check-local.sh — pre-push verification gate for kalign. +# +# Runs four independent test layers; each catches a different class of +# bug. If every required phase passes, the working tree is safe to push. +# +# Usage: +# tests/check-local.sh # run all four phases (~5 min) +# tests/check-local.sh --quick # skip the Linux ASAN container (~30s) +# tests/check-local.sh --help +# +# Phases: +# 1. zig build — cross-compile sanity across aarch64-macos, +# aarch64-linux, x86_64-linux-{gnu,musl}. +# Catches GCC-vs-Clang divergence (e.g. an unused +# variable that is in fact used inside an #ifdef). +# 2. cmake + ctest — native macOS Release build + 15 ctests. +# Algorithmic correctness, ABI, integration. +# 3. podman ASAN — Ubuntu container with kalign built under ASAN +# and the full ctest suite. Catches Linux glibc +# behaviour that Apple's malloc hides (e.g. +# uninitialised MMALLOC fields that read as zero +# on macOS but garbage on Linux). +# 4. pytest — Python bindings, mode presets, ecosystem +# integration. ~171 tests. +# +# Exits 0 only if every non-skipped phase passes. + +set -u +set -o pipefail + +cd "$(dirname "$0")/.." # repo root + +# ---- options ---------------------------------------------------------- +QUICK=0 +for arg in "$@"; do + case "$arg" in + --quick) QUICK=1 ;; + -h|--help) + sed -n '1,30p' "$0" | sed -n 's/^# \{0,1\}//p' + exit 0 + ;; + *) + printf 'Unknown option: %s\n' "$arg" >&2 + printf 'Try: %s --help\n' "$0" >&2 + exit 2 + ;; + esac +done + +# ---- output helpers --------------------------------------------------- +if [ -t 1 ]; then + YELLOW=$'\033[1;33m' + GREEN=$'\033[0;32m' + RED=$'\033[0;31m' + NC=$'\033[0m' +else + YELLOW='' + GREEN='' + RED='' + NC='' +fi + +banner() { + printf '\n%s═══════════════════════════════════════════════════════════════%s\n' "$YELLOW" "$NC" + printf '%s %s%s\n' "$YELLOW" "$1" "$NC" + printf '%s═══════════════════════════════════════════════════════════════%s\n\n' "$YELLOW" "$NC" +} + +# ---- result tracking -------------------------------------------------- +PASS_LIST=() +FAIL_LIST=() +SKIP_LIST=() + +record_pass() { PASS_LIST+=("$1"); } +record_fail() { FAIL_LIST+=("$1"); } +record_skip() { SKIP_LIST+=("$1 — $2"); } + +# ---- Phase 1: zig build ---------------------------------------------- +phase_zig() { + banner "Phase 1/4: zig build (cross-compile sanity)" + if ! command -v zig >/dev/null 2>&1; then + printf '%s⊘ zig not installed — skipping%s\n' "$YELLOW" "$NC" + record_skip "Phase 1 (zig build)" "zig not installed" + return 0 + fi + if zig build; then + record_pass "Phase 1 (zig build)" + return 0 + fi + record_fail "Phase 1 (zig build)" + return 1 +} + +# ---- Phase 2: native CMake + ctest ----------------------------------- +phase_cmake() { + banner "Phase 2/4: native CMake build + ctest" + local ncpu + ncpu="$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4)" + mkdir -p build + if ( cd build \ + && cmake .. -DCMAKE_BUILD_TYPE=Release > /dev/null \ + && make -j"$ncpu" \ + && ctest --output-on-failure ); then + record_pass "Phase 2 (cmake + ctest)" + return 0 + fi + record_fail "Phase 2 (cmake + ctest)" + return 1 +} + +# ---- Phase 3: Linux ASAN container ----------------------------------- +phase_memcheck() { + banner "Phase 3/4: Linux ASAN ctest (podman memcheck container)" + if [ "$QUICK" = "1" ]; then + printf '%s⊘ skipped (--quick)%s\n' "$YELLOW" "$NC" + record_skip "Phase 3 (Linux ASAN)" "--quick" + return 0 + fi + if ! command -v podman >/dev/null 2>&1; then + printf '%s⊘ podman not installed — skipping%s\n' "$YELLOW" "$NC" + record_skip "Phase 3 (Linux ASAN)" "podman not installed" + return 0 + fi + if ! podman info >/dev/null 2>&1; then + printf '%s⊘ podman machine not running — try: podman machine start%s\n' "$YELLOW" "$NC" + record_skip "Phase 3 (Linux ASAN)" "podman machine not running" + return 0 + fi + if ! podman build -f Containerfile.memcheck -t kalign-memcheck . ; then + record_fail "Phase 3 (Linux ASAN — container build)" + return 1 + fi + if podman run --rm kalign-memcheck bash -c \ + "cd /kalign/build-asan && ASAN_OPTIONS='detect_leaks=0:halt_on_error=1:abort_on_error=1' ctest --output-on-failure"; then + record_pass "Phase 3 (Linux ASAN)" + return 0 + fi + record_fail "Phase 3 (Linux ASAN)" + return 1 +} + +# ---- Phase 4: pytest ------------------------------------------------- +phase_python() { + banner "Phase 4/4: Python tests (pytest)" + if ! command -v uv >/dev/null 2>&1; then + printf '%s⊘ uv not installed — skipping%s\n' "$YELLOW" "$NC" + record_skip "Phase 4 (pytest)" "uv not installed" + return 0 + fi + if ! uv pip install -e . \ + --config-settings cmake.args="-DUSE_OPENMP=OFF;-DUSE_THREADPOOL=ON" \ + --force-reinstall --no-deps --quiet; then + record_fail "Phase 4 (pytest — install)" + return 1 + fi + if uv run pytest tests/python/ -q --no-header; then + record_pass "Phase 4 (pytest)" + return 0 + fi + record_fail "Phase 4 (pytest)" + return 1 +} + +# ---- run all phases (each independent; no short-circuit) ------------- +phase_zig || true +phase_cmake || true +phase_memcheck || true +phase_python || true + +# ---- summary --------------------------------------------------------- +banner "Summary" +if [ "${#PASS_LIST[@]}" -gt 0 ]; then + for item in "${PASS_LIST[@]}"; do + printf ' %s✓%s %s\n' "$GREEN" "$NC" "$item" + done +fi +if [ "${#SKIP_LIST[@]}" -gt 0 ]; then + for item in "${SKIP_LIST[@]}"; do + printf ' %s⊘%s %s\n' "$YELLOW" "$NC" "$item" + done +fi +if [ "${#FAIL_LIST[@]}" -gt 0 ]; then + for item in "${FAIL_LIST[@]}"; do + printf ' %s✗%s %s\n' "$RED" "$NC" "$item" + done +fi +echo + +n_fail="${#FAIL_LIST[@]}" +if [ "$n_fail" -eq 0 ]; then + printf '%sAll required phases passed. Safe to push.%s\n' "$GREEN" "$NC" + exit 0 +else + printf '%s%d phase(s) failed. Do NOT push.%s\n' "$RED" "$n_fail" "$NC" + exit 1 +fi diff --git a/tests/dssim.c b/tests/dssim.c index d1c4eb2..9130932 100644 --- a/tests/dssim.c +++ b/tests/dssim.c @@ -105,6 +105,7 @@ int dssim_get_fasta(struct msa **msa, int n_seq, int n_obs, int dna,int len, int m->run_parallel = 0; m->consistency_table = NULL; m->quiet = 1; + m->poar_consistency = NULL; MMALLOC(m->sequences, sizeof(struct msa_seq*) * m->alloc_numseq); for(int i = 0; i < 128; i++){ diff --git a/tests/dssim_test.c b/tests/dssim_test.c index 2c68d19..1673a6e 100644 --- a/tests/dssim_test.c +++ b/tests/dssim_test.c @@ -63,10 +63,17 @@ int test_consistency(int num_tests, int numseq,int dna,int seed) msa_cpy(&m2, m); msa_shuffle_seq(m, rng); msa_shuffle_seq(m2, rng); - kalign_run(m, t1, KALIGN_TYPE_UNDEFINED,0.0,0.0,0.0, 0, 0); - kalign_run(m2, t2, KALIGN_TYPE_UNDEFINED,0.0,0.0,0.0, 0, 0); + { + struct kalign_run_config cfg = kalign_run_config_defaults(); + /* Defaults select a protein matrix; use AUTO so the + library picks the appropriate matrix per biotype + (so this test exercises both protein and DNA). */ + cfg.matrix = KALIGN_MATRIX_AUTO; + RUN(kalign_align_full(m, &cfg, 1, NULL, t1)); + RUN(kalign_align_full(m2, &cfg, 1, NULL, t2)); + } - kalign_msa_compare(m, m2, &score); + RUN(kalign_msa_compare(m, m2, &score)); if(score != 100.0f){ LOG_MSG("Testing %d : %d %d %f", i , t1 ,t2,score); kalign_write_msa(m, NULL, "msf"); diff --git a/tests/kalign_api_test.c b/tests/kalign_api_test.c index 8b107d6..fae60e6 100644 --- a/tests/kalign_api_test.c +++ b/tests/kalign_api_test.c @@ -1,17 +1,17 @@ /* * kalign_api_test.c — comprehensive tests for the kalign public C API. * - * Covers the functions not exercised by the existing test suite: - * - kalign_run_seeded() (VSM, consistency, tree seed/noise) - * - kalign_run_dist_scale() (VSM, seq_weights) - * - kalign_run_realign() (realign iterations) - * - kalign_post_realign() (post-align realign) - * - kalign_run() with refine modes + * Tests the unified kalign_align_full entry point with various configs: + * - Single run with refine modes + * - VSM + seq_weights via run config + * - Seeded tree + consistency anchors + * - Realign iterations + * - Ensemble alignment * - kalign_msa_compare_detailed() * - kalign_msa_compare_with_mask() * - kalign_check_msa() * - reformat_settings_msa() - * - kalign_write_msa() round-trip fasta + * - kalign_write_msa() round-trip fasta * * Each test: * 1. Reads input from the file passed as argv[1] @@ -36,7 +36,6 @@ /* helpers */ /* ------------------------------------------------------------------ */ -/* Count non-gap characters in an aligned sequence string. */ static int count_residues(const char *seq) { int n = 0; @@ -46,7 +45,6 @@ static int count_residues(const char *seq) return n; } -/* Record the ungapped lengths of all sequences before alignment. */ static int *snapshot_lengths(struct msa *m) { int *lens = malloc(sizeof(int) * m->numseq); @@ -57,11 +55,6 @@ static int *snapshot_lengths(struct msa *m) return lens; } -/* Verify basic alignment invariants: - * - alnlen > 0 - * - every seq string has length == alnlen - * - every seq preserves its original residue count - * Returns 0 on success, -1 on failure. */ static int verify_alignment(struct msa *m, int *orig_lens, const char *label) { if (m->alnlen <= 0) { @@ -91,7 +84,6 @@ static int verify_alignment(struct msa *m, int *orig_lens, const char *label) return 0; } -/* Read input, returning a fresh MSA. Caller must free with kalign_free_msa. */ static struct msa *read_input(const char *path) { struct msa *m = NULL; @@ -102,13 +94,19 @@ static struct msa *read_input(const char *path) return m; } +/* Helper: run a single alignment with default config */ +static int align_default(struct msa *msa) +{ + struct kalign_run_config cfg = kalign_run_config_defaults(); + return kalign_align_full(msa, &cfg, 1, NULL, 1); +} + /* ------------------------------------------------------------------ */ /* individual test functions */ /* ------------------------------------------------------------------ */ static int test_run_with_refine(const char *input) { - /* Test KALIGN_REFINE_ALL and KALIGN_REFINE_CONFIDENT */ int modes[] = {KALIGN_REFINE_ALL, KALIGN_REFINE_CONFIDENT}; const char *names[] = {"REFINE_ALL", "REFINE_CONFIDENT"}; @@ -117,9 +115,11 @@ static int test_run_with_refine(const char *input) if (!msa) return -1; int *lens = snapshot_lengths(msa); - int rv = kalign_run(msa, 1, -1, -1, -1, -1, modes[m], 0); + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.refine = modes[m]; + int rv = kalign_align_full(msa, &cfg, 1, NULL, 1); if (rv != 0) { - fprintf(stderr, " kalign_run(%s) returned %d\n", names[m], rv); + fprintf(stderr, " kalign_align_full(%s) returned %d\n", names[m], rv); free(lens); kalign_free_msa(msa); return -1; @@ -142,11 +142,12 @@ static int test_run_dist_scale(const char *input) if (!msa) return -1; int *lens = snapshot_lengths(msa); - /* vsm_amax=2.0, seq_weights=1.0 */ - int rv = kalign_run_dist_scale(msa, 1, -1, -1, -1, -1, 0, 0, - 0.0f, 2.0f, 1.0f); + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.vsm_amax = 2.0f; + cfg.seq_weights = 1.0f; + int rv = kalign_align_full(msa, &cfg, 1, NULL, 1); if (rv != 0) { - fprintf(stderr, " kalign_run_dist_scale returned %d\n", rv); + fprintf(stderr, " align_full(vsm+sw) returned %d\n", rv); free(lens); kalign_free_msa(msa); return -1; @@ -168,11 +169,14 @@ static int test_run_seeded(const char *input) if (!msa) return -1; int *lens = snapshot_lengths(msa); - /* tree_seed=42, tree_noise=0, vsm_amax=2.0, consistency_anchors=5 */ - int rv = kalign_run_seeded(msa, 1, -1, -1, -1, -1, 0, 0, - 42, 0.0f, 0.0f, 2.0f, 0.0f, 5, 2.0f); + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.tree_seed = 42; + cfg.vsm_amax = 2.0f; + cfg.consistency_anchors = 5; + cfg.consistency_weight = 2.0f; + int rv = kalign_align_full(msa, &cfg, 1, NULL, 1); if (rv != 0) { - fprintf(stderr, " kalign_run_seeded returned %d\n", rv); + fprintf(stderr, " align_full(seeded+consistency) returned %d\n", rv); free(lens); kalign_free_msa(msa); return -1; @@ -194,11 +198,12 @@ static int test_run_realign(const char *input) if (!msa) return -1; int *lens = snapshot_lengths(msa); - /* realign_iterations=1, vsm_amax=2.0 */ - int rv = kalign_run_realign(msa, 1, -1, -1, -1, -1, 0, 0, - 0.0f, 2.0f, 1, 0.0f, 0, 2.0f); + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.vsm_amax = 2.0f; + cfg.realign = 1; + int rv = kalign_align_full(msa, &cfg, 1, NULL, 1); if (rv != 0) { - fprintf(stderr, " kalign_run_realign returned %d\n", rv); + fprintf(stderr, " align_full(realign=1) returned %d\n", rv); free(lens); kalign_free_msa(msa); return -1; @@ -214,53 +219,26 @@ static int test_run_realign(const char *input) return 0; } -static int test_post_realign(const char *input) -{ - /* First do a normal alignment, then post-realign the result */ - struct msa *msa = read_input(input); - if (!msa) return -1; - int *lens = snapshot_lengths(msa); - - int rv = kalign_run(msa, 1, -1, -1, -1, -1, 0, 0); - if (rv != 0) { - fprintf(stderr, " initial kalign_run returned %d\n", rv); - free(lens); - kalign_free_msa(msa); - return -1; - } - - rv = kalign_post_realign(msa, 1, -1, -1, -1, -1, 0, 0, - 0.0f, 0.0f, 1, 0.0f); - if (rv != 0) { - fprintf(stderr, " kalign_post_realign returned %d\n", rv); - free(lens); - kalign_free_msa(msa); - return -1; - } - if (verify_alignment(msa, lens, "post_realign") != 0) { - free(lens); - kalign_free_msa(msa); - return -1; - } - fprintf(stdout, " post_realign: OK (alnlen=%d)\n", msa->alnlen); - free(lens); - kalign_free_msa(msa); - return 0; -} - static int test_ensemble_with_realign(const char *input) { struct msa *msa = read_input(input); if (!msa) return -1; int *lens = snapshot_lengths(msa); - /* ensemble=3, vsm=2.0, realign=1, refine=CONFIDENT */ - int rv = kalign_ensemble(msa, 1, -1, 3, -1.0f, -1.0f, -1.0f, - 42, 0, NULL, - KALIGN_REFINE_CONFIDENT, 0.0f, 2.0f, - 1, 0.0f, 0, 2.0f); + /* 3-run ensemble with realign and refine */ + struct kalign_run_config runs[3]; + for (int i = 0; i < 3; i++) { + runs[i] = kalign_run_config_defaults(); + runs[i].vsm_amax = 2.0f; + runs[i].realign = 1; + runs[i].refine = KALIGN_REFINE_CONFIDENT; + runs[i].tree_seed = 42 + (uint64_t)i; + runs[i].tree_noise = (i > 0) ? 0.2f : 0.0f; + } + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + int rv = kalign_align_full(msa, runs, 3, &ens, 1); if (rv != 0) { - fprintf(stderr, " kalign_ensemble+realign returned %d\n", rv); + fprintf(stderr, " align_full(ensemble+realign) returned %d\n", rv); free(lens); kalign_free_msa(msa); return -1; @@ -270,7 +248,7 @@ static int test_ensemble_with_realign(const char *input) kalign_free_msa(msa); return -1; } - /* Also check col_confidence */ + /* Check col_confidence */ if (msa->col_confidence == NULL) { fprintf(stderr, " [ens+realign] FAIL: col_confidence is NULL\n"); free(lens); @@ -294,13 +272,12 @@ static int test_ensemble_with_realign(const char *input) static int test_compare_detailed(const char *input) { - /* Align, then compare to self — should get perfect scores */ struct msa *ref = read_input(input); struct msa *test_msa = read_input(input); if (!ref || !test_msa) return -1; - kalign_run(ref, 1, -1, -1, -1, -1, 0, 0); - kalign_run(test_msa, 1, -1, -1, -1, -1, 0, 0); + align_default(ref); + align_default(test_msa); struct poar_score out; memset(&out, 0, sizeof(out)); @@ -312,7 +289,6 @@ static int test_compare_detailed(const char *input) return -1; } - /* Self-comparison: recall, precision, f1 should all be 1.0 */ if (fabs(out.recall - 1.0) > 0.001) { fprintf(stderr, " FAIL: recall=%.4f (expected 1.0)\n", out.recall); kalign_free_msa(ref); @@ -365,10 +341,9 @@ static int test_compare_with_mask(const char *input) struct msa *test_msa = read_input(input); if (!ref || !test_msa) return -1; - kalign_run(ref, 1, -1, -1, -1, -1, 0, 0); - kalign_run(test_msa, 1, -1, -1, -1, -1, 0, 0); + align_default(ref); + align_default(test_msa); - /* Create a mask that includes all columns */ int n_cols = ref->alnlen; int *mask = malloc(sizeof(int) * n_cols); if (!mask) { @@ -389,7 +364,6 @@ static int test_compare_with_mask(const char *input) return -1; } - /* All columns masked in → same as full comparison → perfect scores */ if (fabs(out.recall - 1.0) > 0.001 || fabs(out.precision - 1.0) > 0.001) { fprintf(stderr, " FAIL: mask-all recall=%.4f prec=%.4f\n", out.recall, out.precision); free(mask); @@ -398,7 +372,7 @@ static int test_compare_with_mask(const char *input) return -1; } - /* Now test with partial mask (first half only) */ + /* Test with partial mask */ for (int i = n_cols / 2; i < n_cols; i++) mask[i] = 0; memset(&out, 0, sizeof(out)); rv = kalign_msa_compare_with_mask(ref, test_msa, mask, n_cols, &out); @@ -409,7 +383,6 @@ static int test_compare_with_mask(const char *input) kalign_free_msa(test_msa); return -1; } - /* Partial mask self-compare should still give perfect scores */ if (fabs(out.recall - 1.0) > 0.001) { fprintf(stderr, " FAIL: partial mask recall=%.4f\n", out.recall); free(mask); @@ -430,8 +403,6 @@ static int test_check_msa(const char *input) struct msa *msa = read_input(input); if (!msa) return -1; - /* kalign_check_msa checks for duplicate sequences. - * Our test files shouldn't have exact duplicates. */ msa->quiet = 1; int rv = kalign_check_msa(msa, 0); if (rv != 0) { @@ -449,22 +420,19 @@ static int test_reformat_settings(const char *input) struct msa *msa = read_input(input); if (!msa) return -1; - /* First align so we have gaps */ - int rv = kalign_run(msa, 1, -1, -1, -1, -1, 0, 0); + int rv = align_default(msa); if (rv != 0) { fprintf(stderr, " initial align failed\n"); kalign_free_msa(msa); return -1; } - /* Test rename */ rv = reformat_settings_msa(msa, 1, 0); if (rv != 0) { fprintf(stderr, " reformat_settings_msa(rename) returned %d\n", rv); kalign_free_msa(msa); return -1; } - /* Verify names were changed to SEQ1, SEQ2, etc. */ for (int i = 0; i < msa->numseq; i++) { char expected[32]; snprintf(expected, sizeof(expected), "SEQ%d", i + 1); @@ -477,16 +445,12 @@ static int test_reformat_settings(const char *input) } fprintf(stdout, " reformat rename: OK\n"); - /* Test unalign — zeroes the gaps[] array and sets status to unaligned. - * Note: dealign_msa operates on the internal gaps[] representation, - * not on seq->seq (which holds finalised text with '-' chars). */ rv = reformat_settings_msa(msa, 0, 1); if (rv != 0) { fprintf(stderr, " reformat_settings_msa(unalign) returned %d\n", rv); kalign_free_msa(msa); return -1; } - /* After unalign, all gaps[] entries should be zero */ for (int i = 0; i < msa->numseq; i++) { for (int j = 0; j <= msa->sequences[i]->len; j++) { if (msa->sequences[i]->gaps[j] != 0) { @@ -505,11 +469,10 @@ static int test_reformat_settings(const char *input) static int test_write_roundtrip(const char *input) { - /* Align, write to fasta, read back, compare */ struct msa *msa = read_input(input); if (!msa) return -1; - int rv = kalign_run(msa, 1, -1, -1, -1, -1, 0, 0); + int rv = align_default(msa); if (rv != 0) { kalign_free_msa(msa); return -1; @@ -523,7 +486,6 @@ static int test_write_roundtrip(const char *input) return -1; } - /* Read it back */ struct msa *msa2 = NULL; rv = kalign_read_input((char *)tmpfile, &msa2, 1); if (rv != 0 || msa2 == NULL) { @@ -533,7 +495,6 @@ static int test_write_roundtrip(const char *input) return -1; } - /* Compare: same number of sequences, same alignment content */ if (msa->numseq != msa2->numseq) { fprintf(stderr, " FAIL: numseq %d vs %d\n", msa->numseq, msa2->numseq); kalign_free_msa(msa); @@ -542,7 +503,6 @@ static int test_write_roundtrip(const char *input) return -1; } - /* Use kalign_msa_compare for a real score check */ float score = 0; rv = kalign_msa_compare(msa, msa2, &score); if (rv != 0) { @@ -586,17 +546,16 @@ int main(int argc, char *argv[]) const char *name; int (*fn)(const char *); } tests[] = { - {"kalign_run + refine", test_run_with_refine}, - {"kalign_run_dist_scale (VSM)", test_run_dist_scale}, - {"kalign_run_seeded (consistency)", test_run_seeded}, - {"kalign_run_realign", test_run_realign}, - {"kalign_post_realign", test_post_realign}, - {"kalign_ensemble + realign", test_ensemble_with_realign}, - {"kalign_msa_compare_detailed", test_compare_detailed}, - {"kalign_msa_compare_with_mask", test_compare_with_mask}, - {"kalign_check_msa", test_check_msa}, - {"reformat_settings_msa", test_reformat_settings}, - {"write fasta roundtrip", test_write_roundtrip}, + {"kalign_align_full + refine", test_run_with_refine}, + {"VSM + seq_weights", test_run_dist_scale}, + {"seeded tree + consistency", test_run_seeded}, + {"realign iterations", test_run_realign}, + {"ensemble + realign", test_ensemble_with_realign}, + {"kalign_msa_compare_detailed", test_compare_detailed}, + {"kalign_msa_compare_with_mask", test_compare_with_mask}, + {"kalign_check_msa", test_check_msa}, + {"reformat_settings_msa", test_reformat_settings}, + {"write fasta roundtrip", test_write_roundtrip}, }; int n_tests = (int)(sizeof(tests) / sizeof(tests[0])); diff --git a/tests/kalign_ensemble_test.c b/tests/kalign_ensemble_test.c index df83a21..8d54fcb 100644 --- a/tests/kalign_ensemble_test.c +++ b/tests/kalign_ensemble_test.c @@ -55,6 +55,19 @@ int main(int argc, char *argv[]) return ret; } +/* Helper: create a 3-run ensemble config with default params */ +static void make_ensemble_runs(struct kalign_run_config *runs, int n_runs, + struct kalign_ensemble_config *ens, int min_support) +{ + for(int i = 0; i < n_runs; i++){ + runs[i] = kalign_run_config_defaults(); + runs[i].tree_seed = 42 + (uint64_t)i; + runs[i].tree_noise = (i > 0) ? 0.2f : 0.0f; + } + *ens = kalign_ensemble_config_defaults(); + ens->min_support = min_support; +} + /* Test 1: Run ensemble alignment with n_runs=3 and verify that * col_confidence is populated with values in [0, 1]. */ static int test_ensemble_confidence(const char *input_file) @@ -68,29 +81,29 @@ static int test_ensemble_confidence(const char *input_file) return -1; } - /* n_runs=3, default gap penalties, seed=42, min_support=0 (auto), no POAR save */ - rv = kalign_ensemble(msa, 1, -1, 3, -1.0f, -1.0f, -1.0f, 42, 0, NULL, 0, 0.0f, -1.0f, 0, 0.0f, 0, 2.0f); + struct kalign_run_config runs[3]; + struct kalign_ensemble_config ens; + make_ensemble_runs(runs, 3, &ens, 0); + + rv = kalign_align_full(msa, runs, 3, &ens, 1); if(rv != 0){ - fprintf(stderr, " ERROR: kalign_ensemble returned %d\n", rv); + fprintf(stderr, " ERROR: kalign_align_full (ensemble) returned %d\n", rv); kalign_free_msa(msa); return -1; } - /* Check that col_confidence was allocated */ if(msa->col_confidence == NULL){ fprintf(stderr, " ERROR: col_confidence is NULL after ensemble\n"); kalign_free_msa(msa); return -1; } - /* Verify alignment length is positive */ if(msa->alnlen <= 0){ fprintf(stderr, " ERROR: alnlen is %d (expected > 0)\n", msa->alnlen); kalign_free_msa(msa); return -1; } - /* Check that all col_confidence values are in [0, 1] */ for(int i = 0; i < msa->alnlen; i++){ float c = msa->col_confidence[i]; if(c < 0.0f || c > 1.0f){ @@ -102,7 +115,6 @@ static int test_ensemble_confidence(const char *input_file) fprintf(stdout, " col_confidence: %d values, all in [0,1]\n", msa->alnlen); - /* Also verify that sequences are aligned (have equal length = alnlen) */ for(int i = 0; i < msa->numseq; i++){ if(msa->sequences[i]->seq == NULL){ fprintf(stderr, " ERROR: sequence %d has NULL seq\n", i); @@ -124,75 +136,58 @@ static int test_ensemble_confidence(const char *input_file) return 0; } -/* Test 2: Run ensemble with save_poar, then load POAR with - * kalign_consensus_from_poar and verify both produce aligned output. */ +/* Test 2: Run ensemble with min_support=2, save POAR, then load POAR with + * kalign_consensus_from_poar and verify both produce matching aligned output. + * + * NOTE: POAR save is no longer in the ensemble config. This test now verifies + * that two ensemble runs with the same params produce identical alignments + * when using explicit min_support (deterministic consensus path). */ static int test_poar_round_trip(const char *input_file) { struct msa *msa1 = NULL; struct msa *msa2 = NULL; int rv; - const char *poar_path = "test_ensemble_poar.bin"; - /* First run: ensemble with POAR save */ rv = kalign_read_input((char *)input_file, &msa1, 1); if(rv != 0 || msa1 == NULL){ fprintf(stderr, " ERROR: failed to read input file: %s\n", input_file); return -1; } - /* Use explicit min_support=2 so the ensemble always takes the consensus - * path. kalign_consensus_from_poar() also requires min_support >= 1, - * and both must use the same threshold for the output to match. */ - rv = kalign_ensemble(msa1, 1, -1, 3, -1.0f, -1.0f, -1.0f, 42, 2, poar_path, 0, 0.0f, -1.0f, 0, 0.0f, 0, 2.0f); - if(rv != 0){ - fprintf(stderr, " ERROR: kalign_ensemble (save) returned %d\n", rv); - kalign_free_msa(msa1); - return -1; - } - - /* Verify the POAR file was created */ - FILE *fp = fopen(poar_path, "rb"); - if(fp == NULL){ - fprintf(stderr, " ERROR: POAR file was not created: %s\n", poar_path); - kalign_free_msa(msa1); - return -1; - } - fclose(fp); - - fprintf(stdout, " POAR saved to %s\n", poar_path); - - /* Second run: load POAR and derive consensus */ rv = kalign_read_input((char *)input_file, &msa2, 1); if(rv != 0 || msa2 == NULL){ - fprintf(stderr, " ERROR: failed to read input for POAR load\n"); + fprintf(stderr, " ERROR: failed to read input (2nd copy)\n"); kalign_free_msa(msa1); return -1; } - rv = kalign_consensus_from_poar(msa2, poar_path, 2); + struct kalign_run_config runs[3]; + struct kalign_ensemble_config ens; + make_ensemble_runs(runs, 3, &ens, 2); + + rv = kalign_align_full(msa1, runs, 3, &ens, 1); if(rv != 0){ - fprintf(stderr, " ERROR: kalign_consensus_from_poar returned %d\n", rv); + fprintf(stderr, " ERROR: kalign_align_full (run 1) returned %d\n", rv); kalign_free_msa(msa1); kalign_free_msa(msa2); return -1; } - /* Verify both MSAs have valid aligned sequences */ - if(msa1->alnlen <= 0){ - fprintf(stderr, " ERROR: msa1 alnlen = %d\n", msa1->alnlen); + rv = kalign_align_full(msa2, runs, 3, &ens, 1); + if(rv != 0){ + fprintf(stderr, " ERROR: kalign_align_full (run 2) returned %d\n", rv); kalign_free_msa(msa1); kalign_free_msa(msa2); return -1; } - if(msa2->alnlen <= 0){ - fprintf(stderr, " ERROR: msa2 alnlen = %d\n", msa2->alnlen); + if(msa1->alnlen <= 0 || msa2->alnlen <= 0){ + fprintf(stderr, " ERROR: alnlen msa1=%d msa2=%d\n", msa1->alnlen, msa2->alnlen); kalign_free_msa(msa1); kalign_free_msa(msa2); return -1; } - /* Both should have the same number of sequences */ if(msa1->numseq != msa2->numseq){ fprintf(stderr, " ERROR: numseq mismatch: %d vs %d\n", msa1->numseq, msa2->numseq); @@ -201,7 +196,6 @@ static int test_poar_round_trip(const char *input_file) return -1; } - /* Both should have the same alignment length (same POAR, same consensus) */ if(msa1->alnlen != msa2->alnlen){ fprintf(stderr, " ERROR: alnlen mismatch: %d vs %d\n", msa1->alnlen, msa2->alnlen); @@ -210,33 +204,20 @@ static int test_poar_round_trip(const char *input_file) return -1; } - /* Verify aligned sequences match between direct ensemble and POAR-loaded consensus */ for(int i = 0; i < msa1->numseq; i++){ - if(msa1->sequences[i]->seq == NULL || msa2->sequences[i]->seq == NULL){ - fprintf(stderr, " ERROR: NULL seq at index %d\n", i); - kalign_free_msa(msa1); - kalign_free_msa(msa2); - return -1; - } if(strcmp(msa1->sequences[i]->seq, msa2->sequences[i]->seq) != 0){ - fprintf(stderr, " ERROR: sequence %d mismatch between ensemble and POAR load\n", i); - fprintf(stderr, " direct: %.60s...\n", msa1->sequences[i]->seq); - fprintf(stderr, " loaded: %.60s...\n", msa2->sequences[i]->seq); + fprintf(stderr, " ERROR: sequence %d mismatch between runs\n", i); kalign_free_msa(msa1); kalign_free_msa(msa2); return -1; } } - fprintf(stdout, " Round-trip: %d sequences, alnlen=%d, consensus matches\n", + fprintf(stdout, " Deterministic: %d sequences, alnlen=%d, both runs match\n", msa1->numseq, msa1->alnlen); kalign_free_msa(msa1); kalign_free_msa(msa2); - - /* Clean up temp file */ - remove(poar_path); - return 0; } @@ -252,22 +233,23 @@ static int test_min_support(const char *input_file) return -1; } - /* min_support=2 (explicit), n_runs=3 */ - rv = kalign_ensemble(msa, 1, -1, 3, -1.0f, -1.0f, -1.0f, 42, 2, NULL, 0, 0.0f, -1.0f, 0, 0.0f, 0, 2.0f); + struct kalign_run_config runs[3]; + struct kalign_ensemble_config ens; + make_ensemble_runs(runs, 3, &ens, 2); + + rv = kalign_align_full(msa, runs, 3, &ens, 1); if(rv != 0){ - fprintf(stderr, " ERROR: kalign_ensemble with min_support=2 returned %d\n", rv); + fprintf(stderr, " ERROR: kalign_align_full with min_support=2 returned %d\n", rv); kalign_free_msa(msa); return -1; } - /* Verify alignment was produced */ if(msa->alnlen <= 0){ fprintf(stderr, " ERROR: alnlen is %d (expected > 0)\n", msa->alnlen); kalign_free_msa(msa); return -1; } - /* Verify sequences are aligned */ for(int i = 0; i < msa->numseq; i++){ if(msa->sequences[i]->seq == NULL){ fprintf(stderr, " ERROR: sequence %d has NULL seq\n", i); @@ -283,7 +265,6 @@ static int test_min_support(const char *input_file) } } - /* Verify col_confidence is populated */ if(msa->col_confidence == NULL){ fprintf(stderr, " ERROR: col_confidence is NULL with min_support=2\n"); kalign_free_msa(msa); diff --git a/tests/kalign_lib_test.c b/tests/kalign_lib_test.c index a7f1081..b6f0f14 100644 --- a/tests/kalign_lib_test.c +++ b/tests/kalign_lib_test.c @@ -15,8 +15,11 @@ int main(int argc, char *argv[]) fprintf(stdout,"reading from %s\n", argv[i]); kalign_read_input(argv[i], &msa,1); } - /* Align seqences */ - kalign_run(msa,1 , -1, -1, -1 , -1, 0, 0); + /* Align sequences */ + { + struct kalign_run_config cfg = kalign_run_config_defaults(); + kalign_align_full(msa, &cfg, 1, NULL, 1); + } /* write alignment in clustal format */ kalign_write_msa(msa, "test.clu", "clu"); /* write alignment in aligned fasta format */ diff --git a/tests/kalign_lib_testCXX.cpp b/tests/kalign_lib_testCXX.cpp index c79367d..18d8950 100644 --- a/tests/kalign_lib_testCXX.cpp +++ b/tests/kalign_lib_testCXX.cpp @@ -7,10 +7,10 @@ int main() { // Initialize array char * inseq[95] = { - "GKGDPKKPRGKMSSYAFFVQTSREEHKKKHPDASVNFSEFSKKCSERWKTMSAKEKGKFEDMAKADKARYEREMKTYIPPKGE", - "MQDRVKRPMNAFIVWSRDQRRKMALENPRMRNSEISKQLGYQWKMLTEAEKWPFFQEAQKLQAMHREKYPNYKYRPRRKAKMLPK", - "MKKLKKHPDFPKKPLTPYFRFFMEKRAKYAKLHPEMSNLDLTKILSKKYKELPEKKKMKYIQDFQREKQEFERNLARFREDHPDLIQNAKK", - "MHIKKPLNAFMLYMKEMRANVVAESTLKESAAINQILGRRWHALSREEQAKYYELARKERQLHMQLYPGWSARDNYGKKKKRKREK", + (char*)"GKGDPKKPRGKMSSYAFFVQTSREEHKKKHPDASVNFSEFSKKCSERWKTMSAKEKGKFEDMAKADKARYEREMKTYIPPKGE", + (char*)"MQDRVKRPMNAFIVWSRDQRRKMALENPRMRNSEISKQLGYQWKMLTEAEKWPFFQEAQKLQAMHREKYPNYKYRPRRKAKMLPK", + (char*)"MKKLKKHPDFPKKPLTPYFRFFMEKRAKYAKLHPEMSNLDLTKILSKKYKELPEKKKMKYIQDFQREKQEFERNLARFREDHPDLIQNAKK", + (char*)"MHIKKPLNAFMLYMKEMRANVVAESTLKESAAINQILGRRWHALSREEQAKYYELARKERQLHMQLYPGWSARDNYGKKKKRKREK", }; int numseq = 4; diff --git a/tests/large_benchmark.c b/tests/large_benchmark.c index 9224787..1c2738a 100644 --- a/tests/large_benchmark.c +++ b/tests/large_benchmark.c @@ -177,7 +177,10 @@ int run_test_aln(struct aln_case *tcase) } /* reference */ RUN(kalign_read_input(path, &r,1)); - kalign_run(t,16 , -1, -1, -1 , -1, 0, 0); + { + struct kalign_run_config cfg = kalign_run_config_defaults(); + kalign_align_full(t, &cfg, 1, NULL, 16); + } diff --git a/tests/memcheck_stress.c b/tests/memcheck_stress.c new file mode 100644 index 0000000..2d1532f --- /dev/null +++ b/tests/memcheck_stress.c @@ -0,0 +1,433 @@ +/* memcheck_stress.c — Comprehensive memory stress test for kalign. + * + * Exercises all major code paths repeatedly to surface memory bugs: + * - In-memory alignment (kalign_arr_to_msa path) + * - File-based alignment (kalign_align_full path) + * - Realignment iterations + * - Refinement (confident, inline) + * - Ensemble alignment with consensus + * - MSA comparison (simple + detailed + with mask) + * - Consistency anchors + * - VSM + seq_weights + * - Align + write + read-back + compare (full benchmark loop) + */ + +#include +#include +#include +#include +#include + +#include "msa_struct.h" +#include "msa_alloc.h" +#include "msa_op.h" +#include "msa_cmp.h" +#include "msa_io.h" + +static int n_passed = 0; +static int n_failed = 0; + +#define RUN_TEST(fn, ...) do { \ + fprintf(stderr, "--- %s ---\n", #fn); \ + if (fn(__VA_ARGS__) == 0) { n_passed++; fprintf(stderr, " PASSED\n"); } \ + else { n_failed++; fprintf(stderr, " *** FAILED ***\n"); } \ +} while(0) + +/* ======== Test helpers ======== */ + +static char* test_protein_seqs[] = { + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAK", + "MKQAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPAAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAK", + "MKTVYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIV", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQD", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKVKALPDAQFEVVHSLAKWKRQQIAATGIQIRGIVKWFNRRKEMISAYDLLAKMKTAY", +}; +static int n_protein_seqs = 8; + +static int get_lens(char** seqs, int n, int* lens) +{ + for (int i = 0; i < n; i++) + lens[i] = (int)strlen(seqs[i]); + return 0; +} + +/* ======== Tests ======== */ + +/* Test 1: Repeated in-memory alignment */ +static int test_inmem_align(int n) +{ + int lens[8]; + get_lens(test_protein_seqs, n_protein_seqs, lens); + + for (int i = 0; i < n; i++) { + char** aligned = NULL; + int aln_len = 0; + int ret = kalign(test_protein_seqs, lens, n_protein_seqs, 1, + KALIGN_TYPE_UNDEFINED, -1.0f, -1.0f, -1.0f, + &aligned, &aln_len); + if (ret != 0) { fprintf(stderr, " failed iter %d\n", i); return 1; } + for (int j = 0; j < n_protein_seqs; j++) free(aligned[j]); + free(aligned); + } + return 0; +} + +/* Test 2: Repeated file-based alignment */ +static int test_file_align(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) { fprintf(stderr, " read failed iter %d\n", i); return 1; } + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { fprintf(stderr, " align failed iter %d\n", i); kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 3: Repeated alignment with realign */ +static int test_realign(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.realign = 1; + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 4: Repeated alignment with refinement */ +static int test_refine(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.refine = KALIGN_REFINE_CONFIDENT; + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 5: Repeated MSA comparison (simple) */ +static int test_compare(const char* ref_file, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* ref = NULL; + struct msa* test = NULL; + float score = 0.0f; + int ret = kalign_read_input((char*)ref_file, &ref, 1); + if (ret != 0 || !ref) return 1; + ret = kalign_read_input((char*)ref_file, &test, 1); + if (ret != 0 || !test) { kalign_free_msa(ref); return 1; } + ret = kalign_msa_compare(ref, test, &score); + kalign_free_msa(ref); + kalign_free_msa(test); + if (ret != 0) return 1; + } + return 0; +} + +/* Test 6: Repeated detailed MSA comparison */ +static int test_compare_detailed(const char* ref_file, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* ref = NULL; + struct msa* test = NULL; + struct poar_score score; + int ret = kalign_read_input((char*)ref_file, &ref, 1); + if (ret != 0 || !ref) return 1; + ret = kalign_read_input((char*)ref_file, &test, 1); + if (ret != 0 || !test) { kalign_free_msa(ref); return 1; } + ret = kalign_msa_compare_detailed(ref, test, 0.2f, &score); + kalign_free_msa(ref); + kalign_free_msa(test); + if (ret != 0) return 1; + } + return 0; +} + +/* Test 7: Repeated ensemble alignment */ +static int test_ensemble(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config runs[3]; + for (int k = 0; k < 3; k++) { + runs[k] = kalign_run_config_defaults(); + runs[k].tree_seed = 42 + (uint64_t)k; + runs[k].tree_noise = (k > 0) ? 0.2f : 0.0f; + } + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + ret = kalign_align_full(msa, runs, 3, &ens, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 8: Full benchmark loop: align + write + read-back + compare */ +static int test_benchmark_loop(const char* input, const char* ref_file, int n) +{ + char tmpfile[] = "/tmp/kalign_memcheck_output.fa"; + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + ret = kalign_write_msa(msa, tmpfile, "fasta"); + kalign_free_msa(msa); + if (ret != 0) return 1; + + /* Compare */ + struct msa* ref = NULL; + struct msa* test = NULL; + float sp = 0.0f; + struct poar_score detailed; + ret = kalign_read_input((char*)ref_file, &ref, 1); + if (ret != 0 || !ref) return 1; + ret = kalign_read_input(tmpfile, &test, 1); + if (ret != 0 || !test) { kalign_free_msa(ref); return 1; } + ret = kalign_msa_compare(ref, test, &sp); + kalign_free_msa(ref); ref = NULL; + kalign_free_msa(test); test = NULL; + if (ret != 0) return 1; + + /* Detailed compare */ + ret = kalign_read_input((char*)ref_file, &ref, 1); + if (ret != 0 || !ref) return 1; + ret = kalign_read_input(tmpfile, &test, 1); + if (ret != 0 || !test) { kalign_free_msa(ref); return 1; } + ret = kalign_msa_compare_detailed(ref, test, 0.2f, &detailed); + kalign_free_msa(ref); ref = NULL; + kalign_free_msa(test); test = NULL; + if (ret != 0) return 1; + } + remove(tmpfile); + return 0; +} + +/* Test 9: Consistency anchors */ +static int test_consistency(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.consistency_anchors = 3; + cfg.consistency_weight = 2.0f; + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 10: Ensemble + realign */ +static int test_ensemble_realign(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config runs[3]; + for (int k = 0; k < 3; k++) { + runs[k] = kalign_run_config_defaults(); + runs[k].realign = 1; + runs[k].refine = KALIGN_REFINE_CONFIDENT; + runs[k].tree_seed = 42 + (uint64_t)k; + runs[k].tree_noise = (k > 0) ? 0.2f : 0.0f; + } + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + ret = kalign_align_full(msa, runs, 3, &ens, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 11: Ensemble + VSM + seq_weights */ +static int test_ensemble_vsm_sw(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config runs[3]; + for (int k = 0; k < 3; k++) { + runs[k] = kalign_run_config_defaults(); + runs[k].vsm_amax = 2.0f; + runs[k].seq_weights = 1.0f; + runs[k].realign = 1; + runs[k].refine = KALIGN_REFINE_CONFIDENT; + runs[k].tree_seed = 42 + (uint64_t)k; + runs[k].tree_noise = (k > 0) ? 0.2f : 0.0f; + } + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + ret = kalign_align_full(msa, runs, 3, &ens, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 12: Inline refinement */ +static int test_inline_refine(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.refine = KALIGN_REFINE_INLINE; + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 13: Mixed parameter variations (like optimizer does) */ +static int test_param_sweep(const char* input, int n) +{ + float vsm_vals[] = {0.0f, 1.0f, 2.0f, 3.0f}; + float sw_vals[] = {0.0f, 1.0f}; + int cons_vals[] = {0, 3, 5}; + int idx = 0; + + for (int i = 0; i < n; i++) { + float vsm = vsm_vals[idx % 4]; + float sw = sw_vals[(idx / 4) % 2]; + int cons = cons_vals[(idx / 8) % 3]; + idx++; + + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.vsm_amax = vsm; + cfg.seq_weights = sw; + cfg.consistency_anchors = cons; + cfg.consistency_weight = 2.0f; + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 14: kalign_align_full with single run configs */ +static int test_align_full_single(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + + struct kalign_run_config cfg = kalign_run_config_defaults(); + cfg.vsm_amax = 2.0f; + cfg.consistency_anchors = 3; + cfg.realign = 1; + cfg.refine = KALIGN_REFINE_CONFIDENT; + + ret = kalign_align_full(msa, &cfg, 1, NULL, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +/* Test 15: kalign_align_full with multi-run configs (ensemble) */ +static int test_align_full_multi(const char* input, int n) +{ + for (int i = 0; i < n; i++) { + struct msa* msa = NULL; + int ret = kalign_read_input((char*)input, &msa, 1); + if (ret != 0 || !msa) return 1; + msa->quiet = 1; + + struct kalign_run_config runs[3]; + for (int k = 0; k < 3; k++) { + runs[k] = kalign_run_config_defaults(); + runs[k].vsm_amax = 2.0f; + runs[k].realign = 1; + runs[k].refine = KALIGN_REFINE_CONFIDENT; + } + runs[1].gpo = 3.0f; runs[1].gpe = 1.5f; runs[1].tgpe = 0.5f; + runs[1].tree_seed = 43; runs[1].tree_noise = 0.2f; + runs[2].gpo = 8.0f; runs[2].gpe = 3.0f; runs[2].tgpe = 1.5f; + runs[2].tree_seed = 44; runs[2].tree_noise = 0.3f; + + struct kalign_ensemble_config ens = kalign_ensemble_config_defaults(); + + ret = kalign_align_full(msa, runs, 3, &ens, 1); + if (ret != 0) { kalign_free_msa(msa); return 1; } + kalign_free_msa(msa); + } + return 0; +} + +int main(int argc, char* argv[]) +{ + int n = 10; + const char* input_file = NULL; + const char* ref_file = NULL; + + if (argc < 3) { + fprintf(stderr, "Usage: %s [n_iters]\n", argv[0]); + return 1; + } + input_file = argv[1]; + ref_file = argv[2]; + if (argc > 3) n = atoi(argv[3]); + + fprintf(stderr, "=== Kalign Memory Stress Test (%d iterations per test) ===\n\n", n); + + RUN_TEST(test_inmem_align, n); + RUN_TEST(test_file_align, input_file, n); + RUN_TEST(test_realign, input_file, n); + RUN_TEST(test_refine, input_file, n); + RUN_TEST(test_compare, ref_file, n); + RUN_TEST(test_compare_detailed, ref_file, n); + RUN_TEST(test_ensemble, input_file, n); + RUN_TEST(test_benchmark_loop, input_file, ref_file, n); + RUN_TEST(test_consistency, input_file, n); + RUN_TEST(test_ensemble_realign, input_file, n); + RUN_TEST(test_ensemble_vsm_sw, input_file, n); + RUN_TEST(test_inline_refine, input_file, n); + RUN_TEST(test_param_sweep, input_file, n * 3); + RUN_TEST(test_align_full_single, input_file, n); + RUN_TEST(test_align_full_multi, input_file, n); + + fprintf(stderr, "\n=== Results: %d passed, %d failed ===\n", n_passed, n_failed); + return n_failed > 0 ? 1 : 0; +} diff --git a/tests/memcheck_stress.py b/tests/memcheck_stress.py new file mode 100644 index 0000000..406acb4 --- /dev/null +++ b/tests/memcheck_stress.py @@ -0,0 +1,220 @@ +"""Python-level memory stress test for kalign. + +Exercises all Python API paths repeatedly to surface memory bugs +in the C library or pybind11 bindings. + +Usage: + python tests/memcheck_stress.py [n_iters] +""" + +import gc +import sys +import tempfile +import traceback +from pathlib import Path + +DATA = Path(__file__).parent / "data" +UNALIGNED = DATA / "BB11001.tfa" +REFERENCE = DATA / "BB11001.msf" +UNALIGNED2 = DATA / "BB30014.tfa" +REFERENCE2 = DATA / "BB30014.msf" + +n_passed = 0 +n_failed = 0 + + +def run_test(fn, *args, **kwargs): + global n_passed, n_failed + name = fn.__name__ + print(f"--- {name} ---", flush=True) + try: + fn(*args, **kwargs) + n_passed += 1 + print(f" PASSED", flush=True) + except Exception as e: + n_failed += 1 + print(f" *** FAILED: {e} ***", flush=True) + traceback.print_exc() + + +def test_align_file_to_file(n): + """Basic align_file_to_file loop.""" + import kalign + for i in range(n): + with tempfile.NamedTemporaryFile(suffix=".fa", delete=True) as tmp: + kalign.align_file_to_file(str(UNALIGNED), tmp.name) + gc.collect() + + +def test_align_file_to_file_with_params(n): + """align_file_to_file with various parameter combinations.""" + import kalign + params_list = [ + {"vsm_amax": 0.0}, + {"vsm_amax": 2.0}, + {"vsm_amax": 2.0, "refine": "confident"}, + {"vsm_amax": 2.0, "realign": 1}, + {"consistency": 3}, + {"consistency": 5, "vsm_amax": 2.0}, + {"seq_weights": 1.0, "vsm_amax": 2.0}, + ] + for i in range(n): + params = params_list[i % len(params_list)] + with tempfile.NamedTemporaryFile(suffix=".fa", delete=True) as tmp: + kalign.align_file_to_file(str(UNALIGNED), tmp.name, **params) + gc.collect() + + +def test_align_inmem(n): + """In-memory alignment via align().""" + import kalign + seqs = [ + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQD", + "MKQAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKV", + "MKTVYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEV", + ] + for i in range(n): + result = kalign.align(seqs, mode="fast") + assert len(result) == len(seqs) + gc.collect() + + +def test_align_inmem_ensemble(n): + """In-memory alignment with ensemble.""" + import kalign + seqs = [ + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQD", + "MKQAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEKAVQVKV", + "MKTVYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEK", + "MKTAYIAKQRQISFVKSHFSRQLEERLGLIEV", + ] + for i in range(n): + result = kalign.align(seqs, ensemble=3, mode="fast") + # ensemble returns (sequences, confidence) + assert isinstance(result, tuple) + gc.collect() + + +def test_align_from_file(n): + """align_from_file loop.""" + import kalign + for i in range(n): + result = kalign.align_from_file(str(UNALIGNED), mode="fast") + assert len(result.names) > 0 + gc.collect() + + +def test_align_from_file_ensemble(n): + """align_from_file with ensemble.""" + import kalign + for i in range(n): + result = kalign.align_from_file(str(UNALIGNED), ensemble=3, mode="fast") + assert result.column_confidence is not None + gc.collect() + + +def test_compare(n): + """Repeated MSA comparison.""" + import kalign + for i in range(n): + score = kalign.compare(str(REFERENCE), str(REFERENCE)) + assert score > 0 + gc.collect() + + +def test_compare_detailed(n): + """Repeated detailed MSA comparison.""" + import kalign + for i in range(n): + result = kalign.compare_detailed(str(REFERENCE), str(REFERENCE)) + assert result["recall"] > 0 + gc.collect() + + +def test_compare_detailed_all_cols(n): + """Repeated detailed comparison with max_gap_frac=-1.0.""" + import kalign + for i in range(n): + result = kalign.compare_detailed(str(REFERENCE), str(REFERENCE), + max_gap_frac=-1.0) + assert result["recall"] > 0 + gc.collect() + + +def test_full_benchmark_loop(n): + """Simulates what the benchmark does: align + compare + compare_detailed.""" + import kalign + for i in range(n): + with tempfile.NamedTemporaryFile(suffix=".fa", delete=True) as tmp: + kalign.align_file_to_file(str(UNALIGNED), tmp.name, mode="fast") + sp = kalign.compare(str(REFERENCE), tmp.name) + detailed = kalign.compare_detailed(str(REFERENCE), tmp.name) + gc.collect() + + +def test_full_benchmark_loop_precise(n): + """Full loop with ensemble+realign (the precise/expensive path).""" + import kalign + for i in range(n): + with tempfile.NamedTemporaryFile(suffix=".fa", delete=True) as tmp: + kalign.align_file_to_file(str(UNALIGNED), tmp.name, + ensemble=3, realign=1, + vsm_amax=2.0, refine="confident", + mode="fast") + sp = kalign.compare(str(REFERENCE), tmp.name) + detailed = kalign.compare_detailed(str(REFERENCE), tmp.name) + gc.collect() + + +def test_generate_sequences(n): + """Repeated sequence generation.""" + import kalign + for i in range(n): + seqs = kalign.generate_test_sequences(20, 10, False, 100, seed=i) + assert len(seqs) == 20 + gc.collect() + + +def test_multiple_datasets(n): + """Alternate between different datasets (like a real benchmark).""" + import kalign + datasets = [ + (UNALIGNED, REFERENCE), + (UNALIGNED2, REFERENCE2), + ] + for i in range(n): + unaln, ref = datasets[i % len(datasets)] + with tempfile.NamedTemporaryFile(suffix=".fa", delete=True) as tmp: + kalign.align_file_to_file(str(unaln), tmp.name, mode="fast") + sp = kalign.compare(str(ref), tmp.name) + detailed = kalign.compare_detailed(str(ref), tmp.name) + gc.collect() + + +def main(): + n = int(sys.argv[1]) if len(sys.argv) > 1 else 10 + print(f"=== Python Memory Stress Test ({n} iters per test) ===\n", flush=True) + + run_test(test_align_file_to_file, n) + run_test(test_align_file_to_file_with_params, n * 2) + run_test(test_align_inmem, n) + run_test(test_align_inmem_ensemble, n) + run_test(test_align_from_file, n) + run_test(test_align_from_file_ensemble, n) + run_test(test_compare, n) + run_test(test_compare_detailed, n) + run_test(test_compare_detailed_all_cols, n) + run_test(test_full_benchmark_loop, n) + run_test(test_full_benchmark_loop_precise, n) + run_test(test_generate_sequences, n) + run_test(test_multiple_datasets, n * 2) + + print(f"\n=== Results: {n_passed} passed, {n_failed} failed ===", flush=True) + sys.exit(1 if n_failed > 0 else 0) + + +if __name__ == "__main__": + main() diff --git a/tests/python/test_add_sequences.py b/tests/python/test_add_sequences.py new file mode 100644 index 0000000..57218ab --- /dev/null +++ b/tests/python/test_add_sequences.py @@ -0,0 +1,180 @@ +"""Tests for adding sequences to an existing alignment.""" +import pytest +import kalign +import os +import tempfile + +TEST_FILE = os.path.join( + os.path.dirname(__file__), "..", "data", "BB11001.tfa" +) + + +def _read_fasta(path): + """Read a FASTA file, return list of (name, sequence) tuples.""" + entries = [] + name = None + seq_parts = [] + with open(path) as f: + for line in f: + if line.startswith('>'): + if name: + entries.append((name, ''.join(seq_parts))) + name = line.strip()[1:] + seq_parts = [] + else: + seq_parts.append(line.strip()) + if name: + entries.append((name, ''.join(seq_parts))) + return entries + + +def _make_existing_and_new(test_file, n_holdout=1): + """Align all sequences, then split into existing alignment + held-out sequences.""" + result = kalign.align_from_file(test_file, mode="fast") + + # Write existing alignment (all but last n_holdout sequences) + existing_path = tempfile.mktemp(suffix=".fa") + with open(existing_path, 'w') as f: + for name, seq in zip(result.names[:-n_holdout], result.sequences[:-n_holdout]): + f.write(f">{name}\n{seq}\n") + + # Write held-out sequences (unaligned, from original file) + names_seqs = [] + with open(test_file) as f: + name = None + seq_parts = [] + for line in f: + if line.startswith('>'): + if name: + names_seqs.append((name, ''.join(seq_parts))) + name = line.strip()[1:] + seq_parts = [] + else: + seq_parts.append(line.strip()) + if name: + names_seqs.append((name, ''.join(seq_parts))) + + new_path = tempfile.mktemp(suffix=".fa") + with open(new_path, 'w') as f: + for name, seq in names_seqs[-n_holdout:]: + f.write(f">{name}\n{seq}\n") + + return existing_path, new_path, result + + +class TestAddSequences: + + def test_basic_add(self): + """Basic add: 3 existing + 1 new = 4 sequences.""" + existing_path, new_path, full_result = _make_existing_and_new(TEST_FILE, 1) + out_path = tempfile.mktemp(suffix=".fa") + + kalign.add_to_alignment(existing_path, new_path, out_path) + + result = _read_fasta(out_path) + assert len(result) == len(full_result.sequences) + + # All sequences should have the same length + lengths = set(len(seq) for _, seq in result) + assert len(lengths) == 1, f"Unequal lengths: {lengths}" + + os.remove(existing_path) + os.remove(new_path) + os.remove(out_path) + + def test_existing_unchanged(self): + """Existing sequences must not be modified (content, ignoring line wrapping).""" + existing_path, new_path, _ = _make_existing_and_new(TEST_FILE, 1) + out_path = tempfile.mktemp(suffix=".fa") + + existing_seqs = _read_fasta(existing_path) + + kalign.add_to_alignment(existing_path, new_path, out_path) + + output_seqs = _read_fasta(out_path) + + for i in range(len(existing_seqs)): + assert existing_seqs[i][1] == output_seqs[i][1], \ + f"Existing sequence {i} was modified!\n before: {existing_seqs[i][1][:60]}...\n after: {output_seqs[i][1][:60]}..." + + os.remove(existing_path) + os.remove(new_path) + os.remove(out_path) + + def test_residue_preservation(self): + """Added sequences must preserve all residues (no residues lost).""" + existing_path, new_path, _ = _make_existing_and_new(TEST_FILE, 1) + out_path = tempfile.mktemp(suffix=".fa") + + # Read original new sequence + with open(new_path) as f: + lines = f.readlines() + orig_seq = ''.join(l.strip() for l in lines if not l.startswith('>')) + orig_residues = len(orig_seq) + + kalign.add_to_alignment(existing_path, new_path, out_path) + + # Read last sequence from output (the added one) + result = _read_fasta(out_path) + added_seq = result[-1][1] + added_residues = sum(1 for c in added_seq if c != '-') + + assert added_residues == orig_residues, \ + f"Residue count changed: {orig_residues} -> {added_residues}" + + os.remove(existing_path) + os.remove(new_path) + os.remove(out_path) + + def test_alignment_length_matches(self): + """Output alignment length must match existing alignment length.""" + existing_path, new_path, _ = _make_existing_and_new(TEST_FILE, 1) + out_path = tempfile.mktemp(suffix=".fa") + + existing_seqs = _read_fasta(existing_path) + existing_alnlen = len(existing_seqs[0][1]) + + kalign.add_to_alignment(existing_path, new_path, out_path) + + output_seqs = _read_fasta(out_path) + output_alnlen = len(output_seqs[0][1]) + + assert output_alnlen == existing_alnlen, \ + f"Alignment length changed: {existing_alnlen} -> {output_alnlen}" + + os.remove(existing_path) + os.remove(new_path) + os.remove(out_path) + + def test_file_not_found(self): + """Should raise FileNotFoundError for missing files.""" + with pytest.raises(FileNotFoundError): + kalign.add_to_alignment("/nonexistent/file.fa", TEST_FILE, "/tmp/out.fa") + with pytest.raises(FileNotFoundError): + kalign.add_to_alignment(TEST_FILE, "/nonexistent/file.fa", "/tmp/out.fa") + + def test_larger_dataset(self): + """Test with BB30014 (44 sequences, hold out 5).""" + test_file = os.path.join( + os.path.dirname(__file__), "..", "data", "BB30014.tfa" + ) + if not os.path.exists(test_file): + pytest.skip("BB30014.tfa not found") + + existing_path, new_path, full_result = _make_existing_and_new(test_file, 5) + out_path = tempfile.mktemp(suffix=".fa") + + kalign.add_to_alignment(existing_path, new_path, out_path) + + result = _read_fasta(out_path) + + # Should have all 44 sequences + assert len(result) == len(full_result.sequences) + + # All same length + lengths = set(len(seq) for _, seq in result) + assert len(lengths) == 1 + + os.remove(existing_path) + os.remove(new_path) + os.remove(out_path) diff --git a/tests/python/test_confidence.py b/tests/python/test_confidence.py new file mode 100644 index 0000000..bda31e0 --- /dev/null +++ b/tests/python/test_confidence.py @@ -0,0 +1,122 @@ +"""Tests for confidence masking and filtering.""" +import pytest +import kalign +import os + +TEST_FILE = os.path.join( + os.path.dirname(__file__), "..", "data", "BB11001.tfa" +) + + +class TestConfidenceMasking: + """Test confidence masking with ensemble modes.""" + + def test_accurate_has_confidence(self): + """Accurate mode should produce confidence scores.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + assert result.column_confidence is not None + assert len(result.column_confidence) > 0 + assert all(0.0 <= c <= 1.0 for c in result.column_confidence) + + def test_fast_has_no_confidence(self): + """Fast mode (single run) should NOT produce confidence.""" + result = kalign.align_from_file(TEST_FILE, mode="fast") + assert result.column_confidence is None + + def test_mask_lowercase(self): + """mask_alignment with lowercase style.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + masked = kalign.mask_alignment(result, threshold=0.5, style="lowercase") + + # Masked sequences should have some lowercase chars + has_lower = any(c.islower() for seq in masked.sequences for c in seq if c != '-') + assert has_lower, "Expected some lowercase residues after masking" + + # Original should be unchanged (uppercase) + has_lower_orig = any(c.islower() for seq in result.sequences for c in seq if c != '-') + assert not has_lower_orig, "Original should not have lowercase" + + def test_mask_remove(self): + """mask_alignment with remove style replaces with gaps.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + masked = kalign.mask_alignment(result, threshold=0.5, style="remove") + + # Masked should have more gaps than original + orig_gaps = sum(seq.count('-') for seq in result.sequences) + masked_gaps = sum(seq.count('-') for seq in masked.sequences) + assert masked_gaps >= orig_gaps + + def test_mask_preserves_confident_residues(self): + """Confident columns should be unchanged after masking.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + masked = kalign.mask_alignment(result, threshold=0.5) + + conf = result.column_confidence + for i, seq in enumerate(result.sequences): + for col in range(len(seq)): + if col < len(conf) and conf[col] >= 0.5 and seq[col] != '-': + assert masked.sequences[i][col] == seq[col], \ + f"Confident residue changed at seq {i} col {col}" + + def test_mask_no_confidence_warns(self): + """mask_alignment on non-ensemble result should warn.""" + result = kalign.align_from_file(TEST_FILE, mode="fast") + with pytest.warns(UserWarning, match="No confidence"): + masked = kalign.mask_alignment(result, threshold=0.5) + # Should return unmodified + assert masked.sequences == result.sequences + + def test_filter_removes_columns(self): + """filter_alignment should remove low-confidence columns.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + filtered = kalign.filter_alignment(result, threshold=0.5) + + # Filtered should be shorter + assert len(filtered.sequences[0]) < len(result.sequences[0]) + + # All sequences should be same length + lengths = [len(s) for s in filtered.sequences] + assert len(set(lengths)) == 1 + + # Filtered confidence should all be >= threshold + assert all(c >= 0.5 for c in filtered.column_confidence) + + def test_filter_preserves_residue_count(self): + """Filtering should not create or destroy residues — only remove columns.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + filtered = kalign.filter_alignment(result, threshold=0.3) + + for i in range(len(result.sequences)): + orig_res = sum(1 for c in result.sequences[i] if c != '-') + filt_res = sum(1 for c in filtered.sequences[i] if c != '-') + # Filtered can have fewer residues (removed columns may have had residues) + assert filt_res <= orig_res + + def test_write_confidence(self, tmp_path): + """write_confidence should produce a file with one float per line.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + outfile = str(tmp_path / "conf.txt") + kalign.write_confidence(outfile, result) + + with open(outfile) as f: + lines = f.readlines() + + assert len(lines) == len(result.column_confidence) + for line in lines: + val = float(line.strip()) + assert 0.0 <= val <= 1.0 + + def test_threshold_zero_is_noop(self): + """Threshold 0 should not mask anything.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + masked = kalign.mask_alignment(result, threshold=0.0) + assert masked.sequences == result.sequences + + def test_threshold_one_masks_some(self): + """Threshold 1.0 should mask at least some columns.""" + result = kalign.align_from_file(TEST_FILE, mode="accurate") + masked = kalign.mask_alignment(result, threshold=1.0) + + # At least some residues should be lowercase + lower_count = sum(1 for seq in masked.sequences for c in seq if c.islower()) + assert lower_count > 0, "Expected some lowercase residues at threshold=1.0" diff --git a/tests/python/test_ecosystem_integration.py b/tests/python/test_ecosystem_integration.py index 0c32b64..ce06892 100644 --- a/tests/python/test_ecosystem_integration.py +++ b/tests/python/test_ecosystem_integration.py @@ -315,9 +315,15 @@ def test_module_exports(self): "REFINE_ALL", "REFINE_CONFIDENT", "REFINE_INLINE", - "MODE_DEFAULT", "MODE_FAST", - "MODE_PRECISE", + "MODE_DEFAULT", + "MODE_RECALL", + "MODE_ACCURATE", + "PROTEIN_CORBLOSUM66", + "add_to_alignment", + "filter_alignment", + "mask_alignment", + "write_confidence", "__version__", "__author__", "__email__", diff --git a/tests/python/test_modes.py b/tests/python/test_modes.py index 63d1645..b549ab4 100644 --- a/tests/python/test_modes.py +++ b/tests/python/test_modes.py @@ -1,4 +1,4 @@ -"""Tests for the unified mode interface (default/fast/precise).""" +"""Tests for the unified mode interface (fast/default/recall/accurate).""" import os import tempfile @@ -22,9 +22,10 @@ class TestModeConstants: """Test mode constant exports.""" def test_mode_constants_exist(self): - assert kalign.MODE_DEFAULT == "default" assert kalign.MODE_FAST == "fast" - assert kalign.MODE_PRECISE == "precise" + assert kalign.MODE_DEFAULT == "default" + assert kalign.MODE_RECALL == "recall" + assert kalign.MODE_ACCURATE == "accurate" class TestAlignModes: @@ -50,10 +51,9 @@ def test_fast_mode(self): assert len(result) == len(TEST_SEQUENCES) assert all(len(s) == len(result[0]) for s in result) - def test_precise_mode(self): - """mode='precise' produces alignment (ensemble).""" - result = kalign.align(TEST_SEQUENCES, mode="precise") - # precise uses ensemble, which returns (seqs, confidence) tuple + def test_recall_mode(self): + """mode='recall' produces alignment.""" + result = kalign.align(TEST_SEQUENCES, mode="recall") if isinstance(result, tuple): seqs = result[0] else: @@ -61,18 +61,20 @@ def test_precise_mode(self): assert len(seqs) == len(TEST_SEQUENCES) assert all(len(s) == len(seqs[0]) for s in seqs) + def test_accurate_mode(self): + """mode='accurate' produces alignment.""" + result = kalign.align(TEST_SEQUENCES, mode="accurate") + if isinstance(result, tuple): + seqs = result[0] + else: + seqs = result + assert len(seqs) == len(TEST_SEQUENCES) + def test_invalid_mode(self): """Invalid mode raises ValueError.""" with pytest.raises(ValueError, match="Invalid mode"): kalign.align(TEST_SEQUENCES, mode="turbo") - def test_explicit_param_overrides_mode(self): - """Explicit consistency=10 overrides fast mode default (consistency=0).""" - # This should not crash — fast base + explicit consistency - result = kalign.align(TEST_SEQUENCES, mode="fast", consistency=10) - assert isinstance(result, list) - assert len(result) == len(TEST_SEQUENCES) - def test_mode_case_insensitive(self): """Mode names are case-insensitive.""" result = kalign.align(TEST_SEQUENCES, mode="FAST") @@ -95,8 +97,8 @@ def test_fast_mode(self): names, sequences = result assert len(names) > 0 - def test_precise_mode(self): - result = kalign.align_from_file(TEST_FILE, mode="precise") + def test_accurate_mode(self): + result = kalign.align_from_file(TEST_FILE, mode="accurate") names, sequences = result assert len(names) > 0 @@ -123,11 +125,11 @@ def test_fast_mode(self): finally: os.unlink(out) - def test_precise_mode(self): + def test_accurate_mode(self): with tempfile.NamedTemporaryFile(suffix=".fa", delete=False) as f: out = f.name try: - kalign.align_file_to_file(TEST_FILE, out, mode="precise") + kalign.align_file_to_file(TEST_FILE, out, mode="accurate") assert os.path.getsize(out) > 0 finally: os.unlink(out) diff --git a/tests/run_memcheck.sh b/tests/run_memcheck.sh new file mode 100644 index 0000000..1a11437 --- /dev/null +++ b/tests/run_memcheck.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# run_memcheck.sh — Run memory checking tools on kalign +# +# Usage: +# ./run_memcheck.sh asan # ASAN + leak detection (C tests) +# ./run_memcheck.sh valgrind # Valgrind (C tests) +# ./run_memcheck.sh python # Python stress test (normal build) +# ./run_memcheck.sh all # All of the above + +set -e + +MODE="${1:-all}" +N_ITERS="${2:-10}" + +TESTDATA="/kalign/tests/data" +INPUT="$TESTDATA/BB11001.tfa" +REF="$TESTDATA/BB11001.msf" +INPUT2="$TESTDATA/BB30014.tfa" +REF2="$TESTDATA/BB30014.msf" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +banner() { + echo "" + echo -e "${YELLOW}============================================================${NC}" + echo -e "${YELLOW} $1${NC}" + echo -e "${YELLOW}============================================================${NC}" + echo "" +} + +# ---- ASAN stress test ---- +run_asan() { + banner "ASAN + Leak Detection (C stress test, $N_ITERS iters)" + + export ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:print_stats=1" + + echo "--- Testing with BB11001 ---" + /kalign/build-asan/memcheck_stress "$INPUT" "$REF" "$N_ITERS" + + echo "" + echo "--- Testing with BB30014 ---" + /kalign/build-asan/memcheck_stress "$INPUT2" "$REF2" "$N_ITERS" + + echo "" + echo "--- ASAN built-in tests ---" + cd /kalign/build-asan && ctest --output-on-failure + + echo -e "\n${GREEN}ASAN tests complete.${NC}" +} + +# ---- Valgrind stress test ---- +run_valgrind() { + banner "Valgrind Memcheck (C stress test, $N_ITERS iters)" + + echo "--- Testing with BB11001 ---" + valgrind --leak-check=full \ + --show-leak-kinds=all \ + --track-origins=yes \ + --error-exitcode=1 \ + --errors-for-leak-kinds=definite \ + /kalign/build-debug/memcheck_stress "$INPUT" "$REF" "$N_ITERS" + + echo "" + echo "--- Testing with BB30014 ---" + valgrind --leak-check=full \ + --show-leak-kinds=all \ + --track-origins=yes \ + --error-exitcode=1 \ + --errors-for-leak-kinds=definite \ + /kalign/build-debug/memcheck_stress "$INPUT2" "$REF2" "$N_ITERS" + + echo -e "\n${GREEN}Valgrind tests complete.${NC}" +} + +# ---- Python stress test ---- +run_python() { + banner "Python Memory Stress Test ($N_ITERS iters)" + + python /kalign/tests/memcheck_stress.py "$N_ITERS" + + echo -e "\n${GREEN}Python stress tests complete.${NC}" +} + +# ---- Main ---- +case "$MODE" in + asan) + run_asan + ;; + valgrind) + run_valgrind + ;; + python) + run_python + ;; + all) + run_asan + run_valgrind + run_python + banner "ALL CHECKS COMPLETE" + ;; + *) + echo "Usage: $0 {asan|valgrind|python|all} [n_iters]" + exit 1 + ;; +esac diff --git a/uv.lock b/uv.lock index 94e9141..3b0ba9e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,9 +2,12 @@ version = 1 revision = 3 requires-python = ">=3.9" resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -12,7 +15,17 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] + +[[package]] +name = "about-time" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3f/ccb16bdc53ebb81c1bf837c1ee4b5b0b69584fd2e4a802a2a79936691c0a/about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece", size = 15380, upload-time = "2022-12-21T04:15:54.991Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/cd/7ee00d6aa023b1d0551da0da5fee3bc23c3eeea632fbfc5126d1fec52b7e/about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341", size = 13295, upload-time = "2022-12-21T04:15:53.613Z" }, ] [[package]] @@ -20,7 +33,8 @@ name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } wheels = [ @@ -32,9 +46,12 @@ name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -48,12 +65,75 @@ 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.16.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "mako", marker = "python_full_version < '3.10'" }, + { name = "sqlalchemy", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/ca/4dc52902cf3491892d464f5265a81e9dff094692c8a049a3ed6a05fe7ee8/alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e", size = 1969868, upload-time = "2025-08-27T18:02:05.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4a/4c61d4c84cfd9befb6fa08a702535b27b21fff08c946bc2f6139decbf7f7/alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3", size = 247355, upload-time = "2025-08-27T18:02:07.37Z" }, +] + +[[package]] +name = "alembic" +version = "1.18.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "mako", marker = "python_full_version >= '3.10'" }, + { name = "sqlalchemy", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, +] + +[[package]] +name = "alive-progress" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "about-time" }, + { name = "graphemeu" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/26/d43128764a6f8fe1668c4f87aba6b1fe52bea81d05a35c84a70d3c70b6f7/alive-progress-3.3.0.tar.gz", hash = "sha256:457dd2428b48dacd49854022a46448d236a48f1b7277874071c39395307e830c", size = 116281, upload-time = "2025-07-20T02:10:39.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/85/ec72f6c885703d18f3b09769645e950e14c7d0cc0a0e35d94127983f666f/alive_progress-3.3.0-py3-none-any.whl", hash = "sha256:63dd33bb94cde15ad9e5b666dbba8fedf71b72a4935d6fb9a92931e69402c9ff", size = 78403, upload-time = "2025-07-20T02:10:37.318Z" }, +] + [[package]] name = "array-api-compat" version = "1.11.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/6c/1e/d04312a19a67744298b7546885149488b8afbb965dfe693aa4964bb60586/array_api_compat-1.11.2.tar.gz", hash = "sha256:a3b7f7b6af18f4c42e79423b1b2479798998b6a74355069d77a01a5282755b5d", size = 50776, upload-time = "2025-03-20T13:11:55.095Z" } wheels = [ @@ -62,12 +142,15 @@ wheels = [ [[package]] name = "array-api-compat" -version = "1.13.0" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -76,9 +159,61 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/68/36/f799b36d7025a92a23819f9f06541babdb84b6fd0bd4253f8be2eca348a4/array_api_compat-1.13.0.tar.gz", hash = "sha256:8b83a56aa8b9477472fee37f7731968dd213e20c198a05ac49caeff9b03f48a6", size = 103065, upload-time = "2025-12-28T11:26:57.734Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/e5/9a12dd1c2b0ad61f3c3ad0fc14b888c65fd735dd9d26805f77317303cbe5/array_api_compat-1.14.0.tar.gz", hash = "sha256:c819ba707f5c507800cb545f7e6348ff1ecc46538381d9ad9b371ffc9cd6d784", size = 106369, upload-time = "2026-02-26T12:02:42.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d3/54cd560804a8c2b898824778e86c13c2a14600bc83532a9c4f69f2f469c3/array_api_compat-1.14.0-py3-none-any.whl", hash = "sha256:ed5af1f9b6595a199c942505f281ec994892556b6efc24679a0501e87a7d6279", size = 60124, upload-time = "2026-02-26T12:02:41.127Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/1f/50f241d4e01fe75f4bba6a209edd4047c4b26acf70992ff885fd161f79cb/ast_serialize-0.4.0.tar.gz", hash = "sha256:74e4e634ab82d1466acf0be27043178570b98ebeaa3165f9240a6fad4c286471", size = 60687, upload-time = "2026-05-14T22:44:38.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/85/232631c59b5ca7152c08f026e9a46f47d852298acff74edd04a1fc1d0005/ast_serialize-0.4.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a6f26937ce0293aafbece0e39019e020369a5a70486ff4088227f0cc888844a9", size = 1182685, upload-time = "2026-05-14T22:43:40.205Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5e/4838d4d3ddc4425555601467d4e2a565e4340899e45feee4e32c80fbc911/ast_serialize-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:074032142777e3e6091977dc3c5146a8ca58ae6825b7f64e9a0b604153ddabd8", size = 1173113, upload-time = "2026-05-14T22:43:41.937Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/d622b19fc1c79a62028ec17f4ad4323177af25b174d32b07c84d61ef9d47/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:404f3462b4532e13a70b8849bba241dbd82e30043ff58d98c7e762fd925b116a", size = 1234117, upload-time = "2026-05-14T22:43:43.977Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b5/72f8c8659da0b64562e6d97f852d5c2022c74577df27c922e1e7065039ce/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:97c55336e16f5c4ca2bde7be94cca4b8f7d665d64f7008925a82e02707ba14ac", size = 1231703, upload-time = "2026-05-14T22:43:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/7b/98/ccc51ee4f90f97a1ed0a0848bd4c9d77a80969849db8a262b7d2970a6a15/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:732b4ef76adcb0f298a7d18c4558336d83b1384f9ae0c7eaa1dc8d031b0a4390", size = 1441574, upload-time = "2026-05-14T22:43:47.784Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ce/668c4efe79e09c9cc97a4d0a1c29e61fe6f78857fe1e57c086772af55f89/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3db87c4772097c0782250bcd550d66b1189a8c889793c7bcf153f4fee70005c", size = 1254040, upload-time = "2026-05-14T22:43:49.879Z" }, + { url = "https://files.pythonhosted.org/packages/3d/be/38b27bc2909b7236939801ca9f0d97cdc6198da4f435a81658e0db506fdb/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43729a5e369ebbe7750635c0c206bc616fcd36e703cb9c4497d6b4df0291ee64", size = 1257847, upload-time = "2026-05-14T22:43:51.607Z" }, + { url = "https://files.pythonhosted.org/packages/68/df/360ebccc361235c167a8be2a0476870cb9ef44c42413bf1289b885684052/ast_serialize-0.4.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:91d3786f3929786cdc4eeedfd110abb4603e7f6c1390c5af398f333a947b742d", size = 1298683, upload-time = "2026-05-14T22:43:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/51/5c/7d5e0b4d47aafa1600c19e3670f962f81a9bf3da1bc25a1382529a447cf3/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7fba7315fd4bd87cb5560792709f6e66e0606402d362c0a38dd32dfb66ba6066", size = 1409438, upload-time = "2026-05-14T22:43:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/8875b2f1af3ec1539b88ff193dfbfa5573084ef7fcab27ea4cd09b6dc829/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4db9769d57deb5545ce56ebbbbe3436dcc0ae2688ce14c295cd14e106624ece7", size = 1507922, upload-time = "2026-05-14T22:43:56.959Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/5ec6927eb493ece7ba64263cdc556be889e0c62a013b1851bbe674a0dcda/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:dcd04f85a29deb80400e8987cfaceb9907140f763453cbffdbd6ff36f1b32c12", size = 1502817, upload-time = "2026-05-14T22:43:59.081Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c8/40cb818a08396b1f34d6189c0c42aec917dd331e11fb7c3b870cc61b795a/ast_serialize-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:905fc11940831454d93589bd7ce2acb6a5eb01c2936156f751d2a21087c98cd3", size = 1454318, upload-time = "2026-05-14T22:44:01.377Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/d51494b60cc52f4792be5ddc951631cddb17a2990154634549abdbdbb5bf/ast_serialize-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:3bdde2c4570143791f636aed4e3ef868f5b46eb90a18f8d5c41dd045aab08bef", size = 1060098, upload-time = "2026-05-14T22:44:03.265Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c9/b0086257c79ff95743a3621448a01fc71b234ae359d3d54cda383aa43939/ast_serialize-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6551d55b8607b97a7755683d743200b398c61a0b71a11b7f00c89c335a11d0f4", size = 1101015, upload-time = "2026-05-14T22:44:05.055Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6d/3dfddef4990fda47745af6615a3e51c4de711eda56c3a8072a0d8b6181c7/ast_serialize-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7234ff086cb152ea2a3b7ef895b5ebeb6d80779df049d5c6431c8e3536d5b03c", size = 1074495, upload-time = "2026-05-14T22:44:07.186Z" }, + { url = "https://files.pythonhosted.org/packages/be/d5/044c5f995ef75807a0effb56fc288cfdedeeb571222450fb6f7d94fd52f1/ast_serialize-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcded5056d9f3d201df7833082c07ebcbc566ffc3d4105c9fc9fe278fa086ecb", size = 1189800, upload-time = "2026-05-14T22:44:09.333Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5a/52163557789d59a8197c10912ab4a1791c9143731ba0e3d9283ac0791db6/ast_serialize-0.4.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bd50d201098aae0d202805fe9606c0545492f69a3ec4403337e32c54ad29fc41", size = 1181713, upload-time = "2026-05-14T22:44:11.286Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c3/678ce3b6cb594b01c361da87f6c5679d26c1dae1583a082a8cd190e7232e/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6615b39cd747967c3aabe68bf3f5f26748e823cc6b474ddc1510ed188a824149", size = 1243258, upload-time = "2026-05-14T22:44:13.345Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/4810fbeb81c47b7e4e65db15ca65c71330efc59b460bd10c12338dc6012e/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91362c0a9fdf1c344b7f50a5b0508b11a0732102998fbd754a191f7187e77031", size = 1239226, upload-time = "2026-05-14T22:44:15.811Z" }, + { url = "https://files.pythonhosted.org/packages/28/38/13a88d90b664c009ed208346ec2ed248b0ab2cb0b582ae467acaa7f44fa4/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70d9c5d527bbfa69bd3c7d17dac11fb6781e36186a434a06d7d5892e0b2f88f9", size = 1448867, upload-time = "2026-05-14T22:44:17.99Z" }, + { url = "https://files.pythonhosted.org/packages/4c/19/a069dba1a634b703bf07fb49df8f7e3c04e9ba8ef3f0d9f4495f72630f92/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4738790cf54d8b416de992b87ee567056980bc82134d52458bd4985f389d1658", size = 1264135, upload-time = "2026-05-14T22:44:19.8Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4c/76ec4279fecd7e78b60c3c99321f944c43cd11e5ff09c952746f5f9c0f4c/ast_serialize-0.4.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:faa008dccfcb793ae9101325e4d6d026caaa5d845c2182f03749c759834b0a3a", size = 1269060, upload-time = "2026-05-14T22:44:21.894Z" }, + { url = "https://files.pythonhosted.org/packages/33/c5/9230ef7481e5cb63b93a1f7738e959586202b081caf32b8bc5d9f673ef56/ast_serialize-0.4.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c5245228e65d38cb48e1251f0ca71b0fa417e527141491e8c92f740e8e2d121", size = 1309654, upload-time = "2026-05-14T22:44:23.725Z" }, + { url = "https://files.pythonhosted.org/packages/b9/54/7d7397528d181ad68e476e0c81aa3ceff7d1f1b5c7fa958d6be28628ef16/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8f5153e9c44a02e61f4042c5f9249d2e8a759773d621a0b2f445a899e536e181", size = 1418855, upload-time = "2026-05-14T22:44:25.415Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8f/87d6428adaa0986b817404f09329b64f8d2614cfe061ebf4951b4a7e0d19/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1e1fb90def261f6a0db885876f7e1a49ad2dbac38ad9f2f62dba2f9543af16e7", size = 1516040, upload-time = "2026-05-14T22:44:27.535Z" }, + { url = "https://files.pythonhosted.org/packages/b5/bb/5aaa41a21314c8b0d6dee54867b16535682c6660dd28cac64dba1380062d/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf2ff7b654c8e95143e20f5d75878cbb78b65b928b26c4d58ef71cdba9d6d981", size = 1511450, upload-time = "2026-05-14T22:44:29.522Z" }, + { url = "https://files.pythonhosted.org/packages/87/16/cc729b5bb4b21da99db1379266cc367512e82ba10f9b3300a6f3e9941325/ast_serialize-0.4.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:90fc5c0d35a22f1a92dd33635508626d50f8fc64deb897c23e78e666a60804c9", size = 1463654, upload-time = "2026-05-14T22:44:31.265Z" }, + { url = "https://files.pythonhosted.org/packages/43/97/7198321b0244d011093387b41affea934d58bda08d59a2adfde72976b6c4/ast_serialize-0.4.0-cp39-abi3-win32.whl", hash = "sha256:9ecd6a1fc1b86f1f4e8ae206759b6319c10019706b3496b01b54d02b9b2cd918", size = 1068636, upload-time = "2026-05-14T22:44:33.189Z" }, + { url = "https://files.pythonhosted.org/packages/10/09/3b868f6d8df4bbe452903a5e0e039ebcec9ea0045f1a77951546205097e8/ast_serialize-0.4.0-cp39-abi3-win_amd64.whl", hash = "sha256:79c8d015c771c8bfdb1208003b227b27c40034790a2c29c09f2317a041825ce2", size = 1107137, upload-time = "2026-05-14T22:44:35.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/78/9387dffccdc55a12734f83aaccc4a987404a217a2a12a1920d8d4585950b/ast_serialize-0.4.0-cp39-abi3-win_arm64.whl", hash = "sha256:1026f565a7ab846337c630909089b3346a2fe417bf1552b1581ab01852137407", size = 1079199, upload-time = "2026-05-14T22:44:36.816Z" }, +] + +[[package]] +name = "autograd" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146, upload-time = "2025-05-05T12:49:02.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/5d/493b1b5528ab5072feae30821ff3a07b7a0474213d548efb1fdf135f85c1/array_api_compat-1.13.0-py3-none-any.whl", hash = "sha256:c15026a0ddec42815383f07da285472e1b1ff2e632eb7afbcfe9b08fcbad9bf1", size = 58585, upload-time = "2025-12-28T11:26:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/84/ea/e16f0c423f7d83cf8b79cae9452040fb7b2e020c7439a167ee7c317de448/autograd-1.8.0-py3-none-any.whl", hash = "sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78", size = 51478, upload-time = "2025-05-05T12:49:00.585Z" }, ] [[package]] @@ -105,17 +240,17 @@ version = "2.1.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "h5py", version = "3.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "h5py", version = "3.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "h5py", version = "3.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cf/63/da18b1cdb9de07f8ad07d71535eac0460d065c71312ad400f55f6c2865ae/biom_format-2.1.17.tar.gz", hash = "sha256:8e3fa07a432b3f6d5c3cad491ef1f27b18d10fc151ca2d223761be4f0b050479", size = 11721758, upload-time = "2025-08-26T15:19:19.35Z" } wheels = [ @@ -151,7 +286,8 @@ name = "biopython" version = "1.85" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -193,12 +329,15 @@ wheels = [ [[package]] name = "biopython" -version = "1.86" +version = "1.87" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -209,56 +348,40 @@ resolution-markers = [ ] dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/61/c59a849bd457c8a1b408ae828dbcc15e674962b5a29705e869e15b32bf25/biopython-1.86.tar.gz", hash = "sha256:93a50b586a4d2cec68ab2f99d03ef583c5761d8fba5535cb8e81da781d0d92ff", size = 19835323, upload-time = "2025-10-28T21:18:31.041Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/9f/5c95732ad98a6d40f4be58978be801cc87b50c71d79a7aee46c4a085114d/biopython-1.86-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02aef2e31cc92544f574ff837cabaaaf53733f3a6b5a433f781c59e5424a7576", size = 2691935, upload-time = "2025-10-28T21:27:12.558Z" }, - { url = "https://files.pythonhosted.org/packages/ca/15/9902cbc901073ba2de397f5c9c84e72147e02aaca1755fa650d26bb715a2/biopython-1.86-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e1b12819a78242b529f54e5d2d00ad90023710a5846ca0f2011ac989fd17d4b", size = 2669420, upload-time = "2025-10-28T21:26:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5d/9cb775106361f8ef7ab459b89ff6d725e81dae7abd382309d3bbf82ced6d/biopython-1.86-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e62504faac6e62fe26e40d6905a69519d8b7b5b0506a426d641b218fde788b5", size = 3183554, upload-time = "2025-10-29T00:35:24.074Z" }, - { url = "https://files.pythonhosted.org/packages/08/47/87c8db55746099b38baf6c597688807bad2e59074cec37169168fe98e69e/biopython-1.86-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d4530060aadc6af060a9a049da91a582738837e187fcea80486c71eca74ae59", size = 3205260, upload-time = "2025-10-29T00:35:33.89Z" }, - { url = "https://files.pythonhosted.org/packages/88/6c/7822f6b7521073ccc80eb18736b664efe1d59edeb6a3f72c362f542ec352/biopython-1.86-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:186e2065c0d1a6c2afc85b9c21a2911a931949668bd73b4c03f429a31b3589f8", size = 3154783, upload-time = "2025-10-28T23:52:52.157Z" }, - { url = "https://files.pythonhosted.org/packages/98/7a/6759f99b481432969a4979f03b4c4966716d4cffd2154749ae5af0d8904a/biopython-1.86-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915a09859159598b9421e7240561692e7bb4084e5340c4dbb2435c5c38805a2", size = 3173920, upload-time = "2025-10-28T23:52:57.611Z" }, - { url = "https://files.pythonhosted.org/packages/17/c2/fe2a223b3fdd718099055679b68f9f15b2006d83a5a1c1593246d5e5fe81/biopython-1.86-cp310-cp310-win32.whl", hash = "sha256:be3d83152fe3232e2d197896a506902b84ad60d40b3f1d1fc934914d138c6dc1", size = 2697949, upload-time = "2025-10-28T21:32:07.305Z" }, - { url = "https://files.pythonhosted.org/packages/1f/11/44fb3975df1070966ffc213f52e9fff63fb48b43bc3d403584ca3f4f9a42/biopython-1.86-cp310-cp310-win_amd64.whl", hash = "sha256:6fbbfe19e12170754adb9632155b7e3be0d4c247f0a2e09d3917bec859282de1", size = 2734235, upload-time = "2025-10-28T21:32:03.014Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f5/37d6bb3a1245ec5f5f1c66d5cd790b06cdb54a75b36849893405c17f3612/biopython-1.86-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba88b0754ad53c93eba11d910364cfc773686933c89a886522309ba903151e50", size = 2691944, upload-time = "2025-10-28T21:27:24.053Z" }, - { url = "https://files.pythonhosted.org/packages/14/12/44d71f333b7302b30788df80705f2207c47b54c17d0935a378dfc709507d/biopython-1.86-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6cceb32b9036bbdc59962e31bd1605ece24edc226c0d50f99839948b5b5c9dda", size = 2669434, upload-time = "2025-10-28T21:26:49.145Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1b/731060090ed29b5ac2484865255f1f363a50afb7275717ceb2c6f20d3ea4/biopython-1.86-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0f040ff85bd7d0ee06574bc6d032bc666802f2fe781b0c316b936237eb3d17e", size = 3196718, upload-time = "2025-10-29T00:35:47.806Z" }, - { url = "https://files.pythonhosted.org/packages/1c/8d/8409535c341061b9c78faf151e73b484b456b3c3bdf59b27cf3984f16fbc/biopython-1.86-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ac858fd71f1093380d8b0a16acf060e7c228ad65f9ecacdb9f5760cfb9f59b1", size = 3218383, upload-time = "2025-10-29T00:35:53.523Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bc/5e93a11f70732122679747a728509d03a6a066b178cc1d7ca30ed2f1ebee/biopython-1.86-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da4bcf5a48ee647624e2d0bedac7fb1c24ef0facd514519cca074593b8a6a40e", size = 3168368, upload-time = "2025-10-28T23:53:16.425Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c6/e187940571a3a24d20f407f1d7514ab1fe0dc9fa49e01790c4bd56ced0bc/biopython-1.86-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d4dd9090caaf364a08ab54cd561f37c5f4ea5bcc8f0189d332dcd36d6df5767", size = 3186451, upload-time = "2025-10-28T23:53:22.463Z" }, - { url = "https://files.pythonhosted.org/packages/2a/88/1e8ffb0db6a03888768613d682a79043e9975067b9095e644a6872905c88/biopython-1.86-cp311-cp311-win32.whl", hash = "sha256:90591f4554c09d311193e7774b5143442c67e178a5b7d929aaa2a054048b22a7", size = 2697756, upload-time = "2025-10-28T21:32:23.017Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b2/e34e45d6cb46c96486a2ed5f07874b6c9493dec68b9d6262ae05f4fe909b/biopython-1.86-cp311-cp311-win_amd64.whl", hash = "sha256:0a95321ca929c04c934e62252c9e2cc5c4fd13ce575798d98af2d79512334b9b", size = 2733781, upload-time = "2025-10-28T21:32:18.409Z" }, - { url = "https://files.pythonhosted.org/packages/98/e2/199b8ccbd4b9bf234157db0668177b5b7784d62f29d9096fd0d3a70e3b86/biopython-1.86-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f8d372aae21d79b11613751c6ae23c88db0e94d25b7567b1f67aa0304fb61667", size = 2693171, upload-time = "2025-10-29T00:26:59.028Z" }, - { url = "https://files.pythonhosted.org/packages/d8/2f/1a7da2a55212b3d0a03866d22213f91273fee3722b5364575419fbe574a5/biopython-1.86-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:baf19d9237aaaa387a68f8f055f978af5c80338d7e037ab028e8d768928f1250", size = 2692543, upload-time = "2025-10-28T21:27:31.855Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e9/4057d4c2aa22ca25c180ecbed2ce9e7d65bf787999778bc63b41df0d03b5/biopython-1.86-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f9abdf6cbf0087850de5f8148da0d420c4cb87905bf4de3145ad24a8d55dcd", size = 2669975, upload-time = "2025-10-28T21:26:54.181Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/3e6862720d7c51f0fbe7d6d25be72a95486779d9d98122283b4e8032fb40/biopython-1.86-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:187c3c24dd2255e7328f3e0523ab5d6350b73ff562517de0c1922385617101d2", size = 3209367, upload-time = "2025-10-29T00:36:06.522Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cb/61877367bf08670573d62513b239dc65cf2b7488dc74322cc6051da2e55e/biopython-1.86-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1859830b8262785c6b59dfe0c82cddb643974f63b9d2779bb9f3e2c47c0a95da", size = 3235466, upload-time = "2025-10-29T00:36:11.516Z" }, - { url = "https://files.pythonhosted.org/packages/84/1a/3182a77776b76f3f5c64825ee1acf9355f665bed72ee9e8ff49e48f25d98/biopython-1.86-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfd906c47b6fb38e3abb9f52e0c06822e6e82a043d38c2000773692c29db1ed8", size = 3178776, upload-time = "2025-10-28T23:53:41.487Z" }, - { url = "https://files.pythonhosted.org/packages/1a/22/828b08fac8dbc8c1dbc1ad03815137cebc9c78303ec7d21b568544028119/biopython-1.86-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a6ab2c60742f1c8494cfbbe3b7a8b45f0400c8f2b36b686b895d5e4d625f04e", size = 3197586, upload-time = "2025-10-28T23:53:47.136Z" }, - { url = "https://files.pythonhosted.org/packages/36/7a/122aea7653fa93d7eb72978928e80759082efffa70afe0c25a17e18521da/biopython-1.86-cp312-cp312-win32.whl", hash = "sha256:192c61bc3d782c171b7d50bb7d8189d84790d6e3c4b24fd41d1d7ffc7d303efe", size = 2698043, upload-time = "2025-10-28T21:32:39.452Z" }, - { url = "https://files.pythonhosted.org/packages/a9/13/00db03b01e54070d5b0ec9c71eef86e61afa733d9af76e5b9b09f5dc9165/biopython-1.86-cp312-cp312-win_amd64.whl", hash = "sha256:35a6b9c5dcdfb5c2631a313a007f3f41a7d72573ba2b68c962e10ea92096ff3b", size = 2733610, upload-time = "2025-10-28T21:32:34.99Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6e/84d6c66ab93095aa7adb998a8eef045328470eafd36b9237c4db213e587c/biopython-1.86-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fb3a11a98e49428720dca227e2a5bdd57c973ee7c4df3cf6734c0aa13fd134c7", size = 2693185, upload-time = "2025-10-28T21:27:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/60386f2640f13765b1651f2f26d8b4f893c46ee663df3ca76eda966d4f6a/biopython-1.86-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e161f3d3b6e65fbfd1ce22a01c3e9fa9da789adde4972fd0cc2370795ea5357b", size = 2669980, upload-time = "2025-10-28T21:26:58.839Z" }, - { url = "https://files.pythonhosted.org/packages/dd/de/a39adb98a0552a257219503c236ef17f007598af55326c0d143db52e5a92/biopython-1.86-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aa8c9e92ee6fe59dfe0d2c2daf9a9eec6b812c78328caad038f79163c500218", size = 3209657, upload-time = "2025-10-29T00:36:28.842Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c7/b2e7aca3de8981f4ecb6ab1e0334c3c4a512e5e9898b57b3d8734b086da7/biopython-1.86-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:593ec6a2a4fedec08ddcee1a8a0e0b0ed56835b2714904b352ec4a93d5b9d973", size = 3235774, upload-time = "2025-10-29T00:36:34.07Z" }, - { url = "https://files.pythonhosted.org/packages/52/ed/e6647b0b9cf2bb67347612e8e443b84378c44768a8d8439276e4ba881178/biopython-1.86-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2f9ebf9b14d67ca92f48779c4f0ba404c35dba3e8b9d6c34d1a3591c3b746d", size = 3178415, upload-time = "2025-10-28T23:54:05.475Z" }, - { url = "https://files.pythonhosted.org/packages/ff/37/f6a14b835842c66a52f212136a99416265f5ce76813d668ceac1cb306357/biopython-1.86-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:137fe9aafd93baa5127d17534b473f6646f92a883f52b34f7c306b800ac50038", size = 3197201, upload-time = "2025-10-28T23:54:10.462Z" }, - { url = "https://files.pythonhosted.org/packages/f2/73/0eac930016c509763c174a0e25e92e6d7a711f6f5de1f7001e54fd5c49f7/biopython-1.86-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e784dc8382430c9893aa084ca18fe8a8815b5811f1c324492ef3f4b54e664fff", size = 3145106, upload-time = "2025-10-28T23:54:15.235Z" }, - { url = "https://files.pythonhosted.org/packages/00/aa/26e836274d03402e8011b04a1714d4ac2f704add303a493e54d2d5646973/biopython-1.86-cp313-cp313-win32.whl", hash = "sha256:5329a777ba90ea624447173046e77c4df2862acc46eea4e94fe2211fe041750f", size = 2698051, upload-time = "2025-10-28T21:32:55.225Z" }, - { url = "https://files.pythonhosted.org/packages/ae/27/fa1f8fa57f2ac8fdc41d14ab36001b8ba0fce5eac01585227b99a4da0e9d/biopython-1.86-cp313-cp313-win_amd64.whl", hash = "sha256:f6f2f1dc75423b15d8a22b8eceae32785736612b6740688526401b8c2d821270", size = 2733649, upload-time = "2025-10-28T21:32:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/a4/2d/5b87ab859d38f2c7d7d1f9df375b4734737c2ef62cf8506983e882419a30/biopython-1.86-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:236ca61aa996f12cbc65a8d6a15abfac70b9ee800656629b784c6a240e7d8dc0", size = 2694733, upload-time = "2025-10-29T00:27:49.142Z" }, - { url = "https://files.pythonhosted.org/packages/24/7e/a80fad6dbfa1335c506b1565d2b3fdd78cda705408a839c5583a9cfca8b6/biopython-1.86-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f96b7441f456c7eecad5c6e61e75b0db1435c489be7cc5e4f97dd4e60921747c", size = 2670131, upload-time = "2025-10-29T00:27:53.758Z" }, - { url = "https://files.pythonhosted.org/packages/2d/0a/6c12e9262b99f395bd66535c4a4203bd70833c11f47ac0730fca6ba2b5f8/biopython-1.86-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d53a78bf960397826219f08f87b061ad7f227527d19986e830eeab60d370b597", size = 3209810, upload-time = "2025-10-29T00:36:45.88Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f9/265211154d2bb4cffe78a57b8e57cfbb165cf41cf3d1b68e2a6b073b3b8a/biopython-1.86-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb86e4383c02fdb2571a38947153346e6f5cd38e22de1df40f54d2a3c51d02a8", size = 3235347, upload-time = "2025-10-29T00:36:51.164Z" }, - { url = "https://files.pythonhosted.org/packages/64/e5/58d8e48d3b4100a7fd8bae97f0dd7179c30f19861841d1a0bb7827e0033e/biopython-1.86-cp314-cp314-win32.whl", hash = "sha256:ffeba620c4786ea836efee235a9c6333b94e922b89de1449a4782dcc15246ff1", size = 2698198, upload-time = "2025-10-29T00:28:02.812Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ca/aa166eb588a2d4eea381c92e5a2a3d09b4b4887b0f0e8f3acf999fb88157/biopython-1.86-cp314-cp314-win_amd64.whl", hash = "sha256:efbb9bc4415a1e2c1c986ba261b02857bc0c9eed098b15493f1cc5c4a1e02409", size = 2734693, upload-time = "2025-10-29T00:27:58.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/da/8c227d701ec9c94d9870b1879982e3dd114da130b0816d3f9b937318d31a/biopython-1.86-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:caa70c1639b3306549605f9273753bdbf8cd6d6d352cecf23afbda3c911694f3", size = 2697389, upload-time = "2025-10-29T00:28:07.037Z" }, - { url = "https://files.pythonhosted.org/packages/8c/1e/66b0b5622ef6a3a14c449d1c8d69749480b37518e4c1e3a8a86fc668dad7/biopython-1.86-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d077f01d1f69f77a26cac46163d4ea45eb4e6509a68feb7f15e665b7e1de0a99", size = 2673857, upload-time = "2025-10-29T00:28:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/76/05/7c8f9800e6960da2007eb75128c8ec0b22e1a0064e8802e8acfad53cdca8/biopython-1.86-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4506ce7dbdf885cb24d1f5439362c3c07f1b6f90761a0d20fe16a2a9ea5702a5", size = 3253007, upload-time = "2025-10-29T00:36:56.066Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/a2177328d841fda0a12e67c65d06279691e25363a2805f561b3665cae114/biopython-1.86-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcd94717e83ba891ebd9acaecbf05ad38313095ca5706caf6c38fa3f2aa17528", size = 3272883, upload-time = "2025-10-29T00:37:01.189Z" }, - { url = "https://files.pythonhosted.org/packages/ce/04/1aa91f64db5e0728d596fcf7302e2ae2035800c0676e94ea09645a948b91/biopython-1.86-cp314-cp314t-win32.whl", hash = "sha256:2f6b205dcb4101cefa5c615114bd35a19f656abb9d340eb3cf190f829e43800a", size = 2701649, upload-time = "2025-10-29T00:28:20.527Z" }, - { url = "https://files.pythonhosted.org/packages/63/7c/4acaca39102d667175bb3d6502dea91c346f8674c06d5df0dbb678971596/biopython-1.86-cp314-cp314t-win_amd64.whl", hash = "sha256:efeee7c37f2331d2c55704df39e122189cc237ffd7511f34158418ad728131b8", size = 2741364, upload-time = "2025-10-29T00:28:15.752Z" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/3e/3c6aa8b2a7e6b791a34407736db32f59657001f0446ada31db73a3e0b7d5/biopython-1.87.tar.gz", hash = "sha256:8456c803459b679a9712422e5a7fd9809f2f089bf69bb085f3b077946ac9bdbf", size = 19855264, upload-time = "2026-03-30T11:28:29.823Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/d6/a01a222dcb2a624f186907f9d7ded1d263ca6164805b0986828f8be3832c/biopython-1.87-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:35f13796188412f135acbed196bd6cfedc1257199d50b4883e289bbd96320efc", size = 2680405, upload-time = "2026-03-30T11:37:21.908Z" }, + { url = "https://files.pythonhosted.org/packages/68/81/46d07aeac54d96e8beb20900fc8223bfae5ca5a9188d70e9f54cf704f36e/biopython-1.87-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63978e1b3ae040c52369bc1bd3f4c668171f5f1f0808798eccd74b575cce455d", size = 3194558, upload-time = "2026-03-30T11:37:27.573Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1e/7f5d251c133fd7f5f7dc57966b2116a15ffd1b8daf0a5144eda7419b2998/biopython-1.87-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e951f4862ffc1dccc28e1c25245059fa653d86028a88f5dfe1b7875875f3a4f", size = 3216226, upload-time = "2026-03-30T11:37:31.172Z" }, + { url = "https://files.pythonhosted.org/packages/23/5b/76a1a5bf4384201f2f146371f67b191b9a5000d7f3be1f962f7e829fecbd/biopython-1.87-cp310-cp310-win32.whl", hash = "sha256:86596b05f39afbc5984ee6239c93fd75f6a97fffb9a630d74b45652c24aab964", size = 2708959, upload-time = "2026-03-30T11:37:39.005Z" }, + { url = "https://files.pythonhosted.org/packages/b1/92/d09b783b7293567c9c662091c6dd69e9630657216deb441d1c4a9b20eeee/biopython-1.87-cp310-cp310-win_amd64.whl", hash = "sha256:efc16dd8a9312eb655fa590821495b0d8ca25d19f7b4ef4fa4da9e71d59d33e5", size = 2745184, upload-time = "2026-03-30T11:37:35.375Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ec/5cab8b1425d7361de75eb7395efb22f0e4a6eb899078fd008d2e748d4962/biopython-1.87-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58e36efa7eaa8813cffe440af4824afa20cc3c21b1b179a95cc0fb15c4b83c01", size = 2680402, upload-time = "2026-03-30T11:37:42.15Z" }, + { url = "https://files.pythonhosted.org/packages/58/09/717d55cc9d74b55fab79b283f1419b1c8785f19875e80f770737645838cc/biopython-1.87-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccf00d15e698656796ab14ff3eb175e282da7a08eedd36a29b6cacec5a33e97f", size = 3207745, upload-time = "2026-03-30T11:37:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c7/06ae2e0672ef5eae35b5858355118cc265553d0ba8a23eaa1c056359683d/biopython-1.87-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bab39ec4108fb2542a9f1012db8269426fd6e86ed84ebc1b0bd11134ef443dd3", size = 3229357, upload-time = "2026-03-30T11:37:48.472Z" }, + { url = "https://files.pythonhosted.org/packages/65/3b/77446506cec866f714e5a7c1f7613ced29762e5a6dfa6be99978dd68a273/biopython-1.87-cp311-cp311-win32.whl", hash = "sha256:126e18ad44e959a1984560562fa4f295c075ce2e610e8ddbc9ec6f52e09f70d8", size = 2708795, upload-time = "2026-03-30T11:37:53.923Z" }, + { url = "https://files.pythonhosted.org/packages/a5/76/12ac6fc6158d0e1ce92f96813dd00ae2770e2c47978c825c904c82058566/biopython-1.87-cp311-cp311-win_amd64.whl", hash = "sha256:d4ff5369ffb7a966bcf921cae6c8a6eab5070b5df1c9f2ae8c18ad40fdcb7ec5", size = 2744744, upload-time = "2026-03-30T11:37:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/18f024658c364196b7f9519674edd3233136fa19874b7ffd9e55ea0fd8e6/biopython-1.87-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:77ccc634621904d4a8fa0a43b5e0f093fa9df8c9577ed3858af648bb3528f51e", size = 2680980, upload-time = "2026-03-30T11:37:58.626Z" }, + { url = "https://files.pythonhosted.org/packages/c1/40/37c45bb4b5e345664bd970c3294349d1e35d4ad5794f808324bbef6ff9e7/biopython-1.87-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3428155c3e0abbed7aad5ff08e034d435b84dfe560c8ec58e7d43abda4b6a43", size = 3220352, upload-time = "2026-03-30T11:38:03.619Z" }, + { url = "https://files.pythonhosted.org/packages/a9/28/7898c2061966d6d62f0bb2e53cd5e1b3bb3053d2bc431f299802faca23cc/biopython-1.87-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:65ba69ef0273e983a9036c2a228142bc34266179a5f03660fc281d332d718630", size = 3246483, upload-time = "2026-03-30T11:38:07.185Z" }, + { url = "https://files.pythonhosted.org/packages/54/d3/a594c8443dc8979b3c051aa266aa4af2d39762c4bb2c37fe6892a19a7713/biopython-1.87-cp312-cp312-win32.whl", hash = "sha256:b077777fd2c555434bdcee58743f6f860aa80e1e005d9671913aa73823c6a773", size = 2709063, upload-time = "2026-03-30T11:38:13.53Z" }, + { url = "https://files.pythonhosted.org/packages/96/1a/d5884c67b20d9ae2b8d93593c971363421fd04c3dc8f5c35530c18e1d6a7/biopython-1.87-cp312-cp312-win_amd64.whl", hash = "sha256:856e3d64f1f27db493474ff84916ed8572731a525e001c7d0d8f41a0fd187000", size = 2743967, upload-time = "2026-03-30T11:38:10.739Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4b/00b8005c24f7c36d8bdffae3354194a2221716004e39029528be923adeae/biopython-1.87-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e05ef5d632c319ab3ef77705c74061190d0792b07e1f2b9eee867401b2758e7e", size = 2680946, upload-time = "2026-03-30T11:38:16.872Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/59115001469e8b3decc8362e1e6e8201acd56cafab95f4f29f4d9994fb4c/biopython-1.87-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:772539297fa16a78f38651c793f53f8c11bd18317b111982e72cf30a6e57512a", size = 3220661, upload-time = "2026-03-30T11:38:20.091Z" }, + { url = "https://files.pythonhosted.org/packages/92/ea/dc2840df6f676d69e898792a0cd6f1217754333ec0003ad3ed5bc7c75da7/biopython-1.87-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d221b2e08e7e89713fdbfb15c8ea6744e908d59f672cd2b6fcf9ed47910d05e", size = 3246766, upload-time = "2026-03-30T11:38:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/6d/98/e7d0e9ed7509798286d6b043fb10a078616aff7c740ad0df506551992b09/biopython-1.87-cp313-cp313-win32.whl", hash = "sha256:fab1b12f6bc4646b7f56b4c390ecff685f02b5b29e3a0c10477195bb49fe62f8", size = 2709036, upload-time = "2026-03-30T11:38:28.822Z" }, + { url = "https://files.pythonhosted.org/packages/93/6e/50d9e4625d687696b3d44bba0d6ab41fe99eee74c97d5d6c5b00c20c03ad/biopython-1.87-cp313-cp313-win_amd64.whl", hash = "sha256:01ee30203bd4b2145cdfe2878499e549a7087f897a6f4d1ebd9de30790123140", size = 2743974, upload-time = "2026-03-30T11:38:26.38Z" }, + { url = "https://files.pythonhosted.org/packages/26/3f/1ed27b0991697993c8f20bcc6b9b55a720993213e4a1aa4803b21366e11b/biopython-1.87-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db73fe16aa2b20677ac86d1997612acb0aa1a3720bc899f65d2bce5583208e1b", size = 2681105, upload-time = "2026-03-30T11:38:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7f/4d405d34d9c6fe1852527eba5dc6c92d333ef739b297df71c771da821ecf/biopython-1.87-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ffe272517478691439a59cccd3cc2929fc8f6bfb8cbc8cc5acc103660395a7", size = 3220706, upload-time = "2026-03-30T11:38:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/9cade51cffba81b3c8dc314f146eb71c46c491d8092fef0f5bc4ccf3a66d/biopython-1.87-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff28a6f31630b3c9f52903478a2ed9dd894b07c1998e40eaeefbeefac20f2d0f", size = 3246379, upload-time = "2026-03-30T11:38:38.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/59/ad1adcd0b9a600ce9fa58ecf1587a75769f05d65320f5daf315ff490fcee/biopython-1.87-cp314-cp314-win32.whl", hash = "sha256:d740c75d4bc94f9dff51719a0deda37e5e885f06ee6dfbb5e9a21bbe9de35a9c", size = 2709283, upload-time = "2026-03-30T11:38:45.886Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e3/313d4002628ffec17745336ccfbfb59d746dec5a022ebd50a9b33c3113c6/biopython-1.87-cp314-cp314-win_amd64.whl", hash = "sha256:98e397096336a49804b6aaaeac8c47ad82e3e4430862f0cde37be73037f1017e", size = 2745825, upload-time = "2026-03-30T11:38:42.285Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/952fb76b38c314c5c7643d55dad10c71d45d5a6c78031795d594a4a1f46f/biopython-1.87-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e4878a9b56775480154c686f81e98f6d907b44d87605bdc2f53538ccdfde9624", size = 2684826, upload-time = "2026-03-30T11:38:48.947Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fb/93e4c0fc762a10cb0d41c9807a87ad6318c4d6d21b272aa160b6394f1ceb/biopython-1.87-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6fcec2e3602ed52ced701f8f7851952383f84dbc4caeb4d202d088170e86b6d", size = 3263913, upload-time = "2026-03-30T11:38:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/63/10/5a2794c558ef6058da8413ad0ffeee2c090d250b9e5d31009faadfe84fc5/biopython-1.87-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8da2d44a4b912c7550a051a5ff4bb72a61decc9c4b19ea92cba4c02fffb143c", size = 3283916, upload-time = "2026-03-30T11:38:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/cd/b18b40e9de9475cfd59f7c30dea6bab7f337758f617d3f5853b1ece8abee/biopython-1.87-cp314-cp314t-win32.whl", hash = "sha256:3670d76759c6cb53ba617f9823d3a438c1aa5415abef6addd29cb81d61d7b312", size = 2712763, upload-time = "2026-03-30T11:39:00.551Z" }, + { url = "https://files.pythonhosted.org/packages/8e/12/478812ef9c8484f96253ba8fb42b466db4f4594c3bb352a5f4318de01704/biopython-1.87-cp314-cp314t-win_amd64.whl", hash = "sha256:331c4151608a1d8406eff0d3c52a0ff1fa3e82604fc85f11c696c562919fb161", size = 2751774, upload-time = "2026-03-30T11:38:57.678Z" }, ] [[package]] @@ -266,7 +389,8 @@ name = "black" version = "25.11.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -309,12 +433,15 @@ wheels = [ [[package]] name = "black" -version = "26.1.0" +version = "26.3.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -324,43 +451,43 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click", version = "8.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, { name = "packaging", marker = "python_full_version >= '3.10'" }, { name = "pathspec", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytokens", marker = "python_full_version >= '3.10'" }, { name = "tomli", marker = "python_full_version == '3.10.*'" }, { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/1b/523329e713f965ad0ea2b7a047eeb003007792a0353622ac7a8cb2ee6fef/black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168", size = 1849661, upload-time = "2026-01-18T04:59:12.425Z" }, - { url = "https://files.pythonhosted.org/packages/14/82/94c0640f7285fa71c2f32879f23e609dd2aa39ba2641f395487f24a578e7/black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d", size = 1689065, upload-time = "2026-01-18T04:59:13.993Z" }, - { url = "https://files.pythonhosted.org/packages/f0/78/474373cbd798f9291ed8f7107056e343fd39fef42de4a51c7fd0d360840c/black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0", size = 1751502, upload-time = "2026-01-18T04:59:15.971Z" }, - { url = "https://files.pythonhosted.org/packages/29/89/59d0e350123f97bc32c27c4d79563432d7f3530dca2bff64d855c178af8b/black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24", size = 1400102, upload-time = "2026-01-18T04:59:17.8Z" }, - { url = "https://files.pythonhosted.org/packages/e1/bc/5d866c7ae1c9d67d308f83af5462ca7046760158bbf142502bad8f22b3a1/black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89", size = 1207038, upload-time = "2026-01-18T04:59:19.543Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, - { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, - { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, - { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, - { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, - { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, - { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, - { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, - { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, - { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, - { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" }, + { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" }, + { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, + { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, + { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, + { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, + { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, + { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, + { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, + { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, ] [[package]] @@ -374,27 +501,62 @@ wheels = [ [[package]] name = "build" -version = "1.4.0" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.10' and os_name == 'nt'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pyproject-hooks", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/18/94eaffda7b329535d91f00fe605ab1f1e5cd68b2074d03f255c7d250687d/build-1.4.0.tar.gz", hash = "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936", size = 50054, upload-time = "2026-01-08T16:41:47.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/ec/bf5ae0a7e5ab57abe8aabdd0759c971883895d1a20c49ae99f8146840c3c/build-1.4.4.tar.gz", hash = "sha256:f832ae053061f3fb524af812dc94b8b84bac6880cd587630e3b5d91a6a9c1703", size = 89220, upload-time = "2026-04-22T20:53:44.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl", hash = "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", size = 24141, upload-time = "2026-01-08T16:41:46.453Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/6764e7a109dd84294850741501145da90d13cdeac9d4e614929464a37420/build-1.4.4-py3-none-any.whl", hash = "sha256:8c3f48a6090b39edec1a273d2d57949aaf13723b01e02f9d518396887519f64d", size = 25921, upload-time = "2026-04-22T20:53:43.251Z" }, +] + +[[package]] +name = "build" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and os_name == 'nt'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.10.2'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pyproject-hooks", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" }, ] [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] @@ -403,151 +565,214 @@ version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, - { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.10.*' and implementation_name != 'PyPy' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and implementation_name != 'PyPy' and sys_platform == 'win32') or (python_full_version >= '3.10' and implementation_name != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, - { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, - { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, - { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, - { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, - { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, - { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/ef725f8eb19b5a261b30f78efa9252ef9d017985cb499102f6f49834cd12/charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217", size = 299121, upload-time = "2026-04-02T09:28:14.372Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/2f12878fbc680fbbb52386cd39a379801f62eaca74fc8b323381325f0f04/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5", size = 200612, upload-time = "2026-04-02T09:28:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b6/10c84e789126ca97d4a7228863a30481e786980a8b8cfcbf4f30658ca63c/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9", size = 221041, upload-time = "2026-04-02T09:28:17.554Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/c414866a138400b2e81973d006da7f694cfeaf895ef07d2cba9a8743841a/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a", size = 216323, upload-time = "2026-04-02T09:28:18.863Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/bdcf94997e06b223d826df3abed45a5ad6e17f609b7df9d25cd23b5bde30/charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc", size = 208419, upload-time = "2026-04-02T09:28:20.332Z" }, + { url = "https://files.pythonhosted.org/packages/1a/64/3f9142293c88b1b10e199649ed1330f070c2a68e305335a5819fa7f25fa7/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00", size = 195016, upload-time = "2026-04-02T09:28:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d1/d8a6b7dd5c5636b76ce0d080bc57d8e56c7bbd6bc2ac941529a35e41d84a/charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776", size = 206115, upload-time = "2026-04-02T09:28:23.259Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8c/60ebe912379627d023eb96995b40bc50308729f210f43d66109ca0a7bbd2/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319", size = 204022, upload-time = "2026-04-02T09:28:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2a/41816ceda78a551cbfdfbeab6f3891152b0e3f758ce6580c2c18c829f774/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24", size = 195914, upload-time = "2026-04-02T09:28:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9b/7c7f4b7f11525fcbdfba752455314ac60646bae91cdd671d531c1f7a97c6/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42", size = 222159, upload-time = "2026-04-02T09:28:27.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/301682e7469bdbfa2ce219a804f0668b2266ab8520570d85d3b3ef483ea3/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4", size = 206154, upload-time = "2026-04-02T09:28:28.848Z" }, + { url = "https://files.pythonhosted.org/packages/20/ec/90339ff5cdc598b265748c1f231c7d7fbd9123a92cee10f757e0b1448de4/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67", size = 217423, upload-time = "2026-04-02T09:28:30.248Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e7/a7a6147f8e3375676309cf584b25c72a3bab784ea4085b0011fa07b23aeb/charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274", size = 210604, upload-time = "2026-04-02T09:28:31.736Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/d9340c7a79c393e57807d7fb6c57e82060687891f81b74d3201958b919c1/charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366", size = 144631, upload-time = "2026-04-02T09:28:33.158Z" }, + { url = "https://files.pythonhosted.org/packages/21/e7/92901117e2ddc8facfe8235a3ecd4eb482185b2ad5d5b6606b37c1afea06/charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444", size = 154710, upload-time = "2026-04-02T09:28:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4f/e1fb138201ad9a32499dd9a98aa4a5a5441fbf7f56b52b619a54b7ee8777/charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c", size = 143716, upload-time = "2026-04-02T09:28:35.908Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] @@ -555,7 +780,8 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, @@ -567,12 +793,15 @@ wheels = [ [[package]] name = "click" -version = "8.3.1" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -584,9 +813,23 @@ resolution-markers = [ dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, +] + +[[package]] +name = "cma" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ac/8c27720838e293898671f01b5c452236a0c74f4799a3f2d5fcccbbf50d71/cma-4.4.4.tar.gz", hash = "sha256:632bd654b5dce04c0eaa3166679d3e4773ce7a79eab7934e7f363c341b9a8170", size = 316645, upload-time = "2026-02-25T22:18:16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/d4/ec46cedab6a6145e21768baa8110db3e2e836a320d8499e4ef18bc894e61/cma-4.4.4-py3-none-any.whl", hash = "sha256:edb6d02eb2aac2d54650f16a8f0c70711ff17445957de7c9de92ff7fd4b7ef38", size = 328311, upload-time = "2026-02-25T22:18:09.602Z" }, ] [[package]] @@ -598,12 +841,25 @@ 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.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + [[package]] name = "contourpy" version = "1.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -751,9 +1007,12 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -762,7 +1021,7 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -844,7 +1103,8 @@ name = "coverage" version = "7.10.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } wheels = [ @@ -960,12 +1220,15 @@ toml = [ [[package]] name = "coverage" -version = "7.13.3" +version = "7.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -974,99 +1237,113 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, - { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, - { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, - { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, - { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, - { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, - { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, - { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, - { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, - { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, - { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, - { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, - { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, - { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, - { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, - { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, - { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, - { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, - { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, - { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, - { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, - { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, - { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, - { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, - { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, - { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, - { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, - { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, - { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, - { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, - { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, - { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, - { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, - { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, - { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, - { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, - { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, - { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, - { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, - { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, - { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, - { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, - { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, - { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, - { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, - { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, - { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, - { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/9d/7c83ef51c3eb495f10010094e661833588b7709946da634c8b66520b97c7/coverage-7.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075", size = 219668, upload-time = "2026-05-10T17:59:23.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/34/898546aefbd28f0af131201d0dc852c9e976f817bd7d5bfb8dc4e02863bb/coverage-7.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82", size = 220192, upload-time = "2026-05-10T17:59:26.095Z" }, + { url = "https://files.pythonhosted.org/packages/df/4a/b457c88aca72b0df13a98167ebd5d947135ccd9881ea88ce6a570e13aa9b/coverage-7.14.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c", size = 246932, upload-time = "2026-05-10T17:59:27.806Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d9/92600e89486fd074c50f0117422b2c9592c3e144e2f25bd5ac0bc62bc7a0/coverage-7.14.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893", size = 248762, upload-time = "2026-05-10T17:59:29.479Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e1/9ea1eb9c311da7f15853559dc1d9d82bef88ecd3e59fbeb51f16bc2ffa91/coverage-7.14.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20", size = 250625, upload-time = "2026-05-10T17:59:31.33Z" }, + { url = "https://files.pythonhosted.org/packages/a5/03/57afca1b8106f8549a5329139315041fe166d6099bd9381346b9430dfbd1/coverage-7.14.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec", size = 252539, upload-time = "2026-05-10T17:59:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/2e9fc63c9928119c1dbae02222be51407d3e7ebac5811ebbda4af3557795/coverage-7.14.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757", size = 247636, upload-time = "2026-05-10T17:59:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/0b7898cda21041cc67546e19b80ba66cbbb47cbece52a76a5904de6a3aaf/coverage-7.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a", size = 248666, upload-time = "2026-05-10T17:59:36.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/d33662a2fdaef23229c15921f39c84ec38441f3069ba26e134ed402c833b/coverage-7.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea", size = 246670, upload-time = "2026-05-10T17:59:38.029Z" }, + { url = "https://files.pythonhosted.org/packages/99/b2/533942c3bfbf6770b5c32d7f2ff029fe013dba31f3fe8b45cabbb250365e/coverage-7.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb", size = 250484, upload-time = "2026-05-10T17:59:39.974Z" }, + { url = "https://files.pythonhosted.org/packages/d8/00/15acbad83a96de13c73831486c7627bfed73dfaec53b04e4a6315edf3fd8/coverage-7.14.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218", size = 246942, upload-time = "2026-05-10T17:59:41.659Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/cef0228de493f2c740c760a9057a61d00c6849480073b70a75b87c7d4bab/coverage-7.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85", size = 247544, upload-time = "2026-05-10T17:59:43.471Z" }, + { url = "https://files.pythonhosted.org/packages/77/a0/d9ef8e148f3025c2ae8401d77cda1502b6d2a4d8102603a8af31460aedb6/coverage-7.14.0-cp310-cp310-win32.whl", hash = "sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323", size = 222285, upload-time = "2026-05-10T17:59:44.908Z" }, + { url = "https://files.pythonhosted.org/packages/85/c0/30c454c7d3cf47b2805d4e06f12443f5eece8a5d030d3b0350e7b74ecb49/coverage-7.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a", size = 223215, upload-time = "2026-05-10T17:59:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e4/649c8d4f7f1709b6dbfc474358aa1bba02f67bcd52e2fec291a5014006cd/coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", size = 219795, upload-time = "2026-05-10T17:59:48.198Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", size = 220299, upload-time = "2026-05-10T17:59:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/12/c2/a40f5cb295bbcbb697a76947a56081c494c61950366294ee426ffe261099/coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", size = 250721, upload-time = "2026-05-10T17:59:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", size = 252633, upload-time = "2026-05-10T17:59:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/bb/80/5f596e8995785124ee191c42535664c5e62c65995b66f4ca21e28ae04c81/coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", size = 254743, upload-time = "2026-05-10T17:59:55.021Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6d/0d178825be2350f0adb27984d0aa7cf84bbdab201f6fb926b535d23a8f5f/coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", size = 256700, upload-time = "2026-05-10T17:59:56.511Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/9e549c2f6e9dfea472adadba06c294e64735dabc2dd19015fac082095013/coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", size = 250854, upload-time = "2026-05-10T17:59:57.94Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1c/b94f9f5f36396021ee2f62c5834b12e6a3d31f0bed5d6fc6d1c3caec087c/coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", size = 252433, upload-time = "2026-05-10T17:59:59.688Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cb/d192cd8e1345eccabc32016f2d39072ecd10cb4f4b983ed8d0ebdeaf00dc/coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", size = 250494, upload-time = "2026-05-10T18:00:01.953Z" }, + { url = "https://files.pythonhosted.org/packages/53/c5/aac9f460a41d835dbddef1d377f105f6ac2311d0f3c1588e9f51046d8813/coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", size = 254261, upload-time = "2026-05-10T18:00:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/23/aa/7af7c0081980a9cb3d289c5a435a4b7657dcecbd128e25c580e6a50389b5/coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", size = 250216, upload-time = "2026-05-10T18:00:05.262Z" }, + { url = "https://files.pythonhosted.org/packages/35/60/a4257538ce2f6b978aeb51870d6c4208c510928a03db7e0339bb625dccb7/coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", size = 251125, upload-time = "2026-05-10T18:00:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ab/f91af47642ec1aa53490e835a95847168d9c77fc39aa58527604c051e145/coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", size = 222300, upload-time = "2026-05-10T18:00:08.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", size = 223241, upload-time = "2026-05-10T18:00:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/d8/6e/d9d312a5151a96cd110efee32efc3fc97b01ebd86203fe618ccb29cf4c92/coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", size = 221908, upload-time = "2026-05-10T18:00:12.242Z" }, + { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, + { url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z" }, + { url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z" }, + { url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z" }, + { url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z" }, + { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, + { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z" }, + { url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z" }, + { url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z" }, + { url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z" }, + { url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z" }, + { url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" }, + { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" }, + { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" }, + { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" }, + { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" }, + { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" }, + { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] [package.optional-dependencies] @@ -1076,45 +1353,99 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.4" +version = "47.0.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version <= '3.9'", +] dependencies = [ - { name = "cffi", marker = "(python_full_version < '3.11' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten') or (python_full_version < '3.11' and platform_python_implementation != 'PyPy' and sys_platform == 'win32') or (platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "cffi", marker = "python_full_version <= '3.9' and platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version <= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, + { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, + { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, + { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, + { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/81/75/d691e284750df5d9569f2b1ce4a00a71e1d79566da83b2b3e5549c84917f/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1a405c08857258c11016777e11c02bacbe7ef596faf259305d282272a3a05cbe", size = 4587867, upload-time = "2026-04-24T19:54:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/07/d6/1b90f1a4e453009730b4545286f0b39bb348d805c11181fc31544e4f9a65/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:20fdbe3e38fb67c385d233c89371fa27f9909f6ebca1cecc20c13518dae65475", size = 4627192, upload-time = "2026-04-24T19:54:42.849Z" }, + { url = "https://files.pythonhosted.org/packages/dc/53/cb358a80e9e359529f496870dd08c102aa8a4b5b9f9064f00f0d6ed5b527/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50", size = 4587486, upload-time = "2026-04-24T19:54:44.908Z" }, + { url = "https://files.pythonhosted.org/packages/8b/57/aaa3d53876467a226f9a7a82fd14dd48058ad2de1948493442dfa16e2ffd/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9fe6b7c64926c765f9dff301f9c1b867febcda5768868ca084e18589113732ab", size = 4626327, upload-time = "2026-04-24T19:54:47.813Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, - { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, - { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, - { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, - { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, - { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, - { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, - { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, - { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, - { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, - { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, - { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, - { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, - { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, - { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, + +[[package]] +name = "cryptography" +version = "48.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", + "python_full_version > '3.9' and python_full_version < '3.10'", +] +dependencies = [ + { name = "cffi", marker = "(python_full_version > '3.9' and python_full_version < '3.11' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten') or (python_full_version > '3.9' and python_full_version < '3.11' and platform_python_implementation != 'PyPy' and sys_platform == 'win32') or (python_full_version > '3.9' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "typing-extensions", marker = "python_full_version > '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" }, ] [[package]] @@ -1128,22 +1459,24 @@ wheels = [ [[package]] name = "dash" -version = "4.0.0" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, - { name = "importlib-metadata" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "nest-asyncio" }, { name = "plotly" }, - { name = "requests" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "retrying" }, { name = "setuptools" }, { name = "typing-extensions" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/dd/3aed9bfd81dfd8f44b3a5db0583080ac9470d5e92ee134982bd5c69e286e/dash-4.0.0.tar.gz", hash = "sha256:c5f2bca497af288f552aea3ae208f6a0cca472559003dac84ac21187a1c3a142", size = 6943263, upload-time = "2026-02-03T19:42:27.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/da/a13ae3a6528bd51a6901461dbff4549c6009de203d6249a89b9a09ac5cfb/dash-4.1.0.tar.gz", hash = "sha256:17a92a87b0c1eacc025079a705e44e72cd4c5794629c0a2909942b611faeb595", size = 6927689, upload-time = "2026-03-23T20:39:47.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/8c/dd63d210b28a7589f4bc1e84880525368147425c717d12834ab562f52d14/dash-4.0.0-py3-none-any.whl", hash = "sha256:e36b4b4eae9e1fa4136bf4f1450ed14ef76063bc5da0b10f8ab07bd57a7cb1ab", size = 7247521, upload-time = "2026-02-03T19:42:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/2a/00/10b1f8b3885fc4add1853e9603af15c593fa0be20d37c158c4d811e868dc/dash-4.1.0-py3-none-any.whl", hash = "sha256:1af9f302bc14061061012cdb129b7e370d3604b12a7f730b252ad8e4966f01f7", size = 7232489, upload-time = "2026-03-23T20:39:40.658Z" }, ] [[package]] @@ -1155,13 +1488,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + [[package]] name = "docutils" version = "0.21.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] 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 = [ @@ -1173,9 +1528,12 @@ name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1225,21 +1583,21 @@ wheels = [ [[package]] name = "flask" -version = "3.1.2" +version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "itsdangerous" }, { name = "jinja2" }, { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] [[package]] @@ -1247,7 +1605,8 @@ name = "fonttools" version = "4.60.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/3e/c4/db6a7b5eb0656534c3aa2596c2c5e18830d74f1b9aa5aa8a7dff63a0b11d/fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c", size = 3562254, upload-time = "2025-12-09T13:38:11.835Z" } wheels = [ @@ -1312,12 +1671,148 @@ wheels = [ [[package]] name = "fonttools" -version = "4.61.1" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +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 = "graphemeu" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/20/d012f71e7d00e0d5bb25176b9a96c5313d0e30cf947153bfdfa78089f4bb/graphemeu-0.7.2.tar.gz", hash = "sha256:42bbe373d7c146160f286cd5f76b1a8ad29172d7333ce10705c5cc282462a4f8", size = 307626, upload-time = "2025-01-15T09:48:59.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/18/36503ea63e1ecd0a95590d7b6b8b7d227a1e4541a154e1612a231def1bdc/graphemeu-0.7.2-py3-none-any.whl", hash = "sha256:1444520f6899fd30114fc2a39f297d86d10fa0f23bf7579f772f8bc7efaa2542", size = 22670, upload-time = "2025-01-15T09:48:57.241Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/f5/3e9eafb4030588337b2a2ae4df46212956854e9069c07b53aa3caabafd47/greenlet-3.2.5.tar.gz", hash = "sha256:c816554eb33e7ecf9ba4defcb1fd8c994e59be6b4110da15480b3e7447ea4286", size = 191501, upload-time = "2026-02-20T20:08:51.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/d6/b3db928fc329b1b19ba32ffe143d2305f3aaafc583f5e1074c74ec445189/greenlet-3.2.5-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:34cc7cf8ab6f4b85298b01e13e881265ee7b3c1daf6bc10a2944abc15d4f87c3", size = 275803, upload-time = "2026-02-20T20:06:42.541Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/ab0ad4ff3d9e1faa266de4f6c79763b33fccd9265995f2940192494cc0ec/greenlet-3.2.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c11fe0cfb0ce33132f0b5d27eeadd1954976a82e5e9b60909ec2c4b884a55382", size = 633556, upload-time = "2026-02-20T20:30:41.594Z" }, + { url = "https://files.pythonhosted.org/packages/da/dd/7b3ac77099a1671af8077ecedb12c9a1be1310e4c35bb69fd34c18ab6093/greenlet-3.2.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a145f4b1c4ed7a2c94561b7f18b4beec3d3fb6f0580db22f7ed1d544e0620b34", size = 644943, upload-time = "2026-02-20T20:37:23.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/36/84630e9ff1dfc8b7690957c0f77834a84eabdbd9c4977c3a2d0cbd5325c2/greenlet-3.2.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc1d01bdd67db3e5711e6246e451d7a0f75fae7bbf40adde129296a7f9aa7cc9", size = 639841, upload-time = "2026-02-20T20:07:17.473Z" }, + { url = "https://files.pythonhosted.org/packages/12/c4/6a2ee6c676dea7a05a3c3c1291fbc8ea44f26456b0accc891471293825af/greenlet-3.2.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd593db7ee1fa8a513a48a404f8cc4126998a48025e3f5cbbc68d51be0a6bf66", size = 588813, upload-time = "2026-02-20T20:07:56.171Z" }, + { url = "https://files.pythonhosted.org/packages/01/c0/75e75c2c993aa850292561ec80f5c263e3924e5843aa95a38716df69304c/greenlet-3.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac8db07bced2c39b987bba13a3195f8157b0cfbce54488f86919321444a1cc3c", size = 1117377, upload-time = "2026-02-20T20:32:48.452Z" }, + { url = "https://files.pythonhosted.org/packages/ee/03/e38ebf9024a0873fe8f60f5b7bc36bfb3be5e13efe4d798240f2d1f0fb73/greenlet-3.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4544ab2cfd5912e42458b13516429e029f87d8bbcdc8d5506db772941ae12493", size = 1141246, upload-time = "2026-02-20T20:06:23.576Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7b/c6e1192c795c0c12871e199237909a6bd35757d92c8472c7c019959b8637/greenlet-3.2.5-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:acabf468466d18017e2ae5fbf1a5a88b86b48983e550e1ae1437b69a83d9f4ac", size = 276916, upload-time = "2026-02-20T20:06:18.166Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b6/9887b559f3e1952d23052ec352e9977e808a2246c7cb8282a38337221e88/greenlet-3.2.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:472841de62d60f2cafd60edd4fd4dd7253eb70e6eaf14b8990dcaf177f4af957", size = 636107, upload-time = "2026-02-20T20:30:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/8a/be/e3e48b63bbc27d660fa1d98aecb64906b90a12e686a436169c1330ef34b2/greenlet-3.2.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d951e7d628a6e8b68af469f0fe4f100ef64c4054abeb9cdafbfaa30a920c950", size = 648240, upload-time = "2026-02-20T20:37:24.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ac/e731ed62576e91e533b36d0d97325adc2786674ab9e48ed8a6a24f4ef4e9/greenlet-3.2.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8317d732e2ae0935d9ed2af2ea876fa714cf6f3b887a31ca150b54329b0a6e9", size = 643313, upload-time = "2026-02-20T20:07:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/64/99e5cdceb494bd4c1341c45b93f322601d2c8a5e1e4d1c7a2d24c5ed0570/greenlet-3.2.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce8aed6fdd5e07d3cbb988cbdc188266a4eb9e1a52db9ef5c6526e59962d3933", size = 591295, upload-time = "2026-02-20T20:07:57.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e9/968e11f388c2b8792d3b8b40a57984c894a3b4745dae3662dce722653bc5/greenlet-3.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:60c06b502d56d5451f60ca665691da29f79ed95e247bcf8ce5024d7bbe64acb9", size = 1120277, upload-time = "2026-02-20T20:32:50.103Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2c/b5f2c4c68d753dce08218dc5a6b21d82238fdfdc44309032f6fe24d285e6/greenlet-3.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d2a78e6f1bf3f1672df91e212a2f8314e1e7c922f065d14cbad4bc815059467", size = 1145746, upload-time = "2026-02-20T20:06:26.296Z" }, + { url = "https://files.pythonhosted.org/packages/ad/32/022b21523eee713e7550162d5ca6aed23f913cc2c6232b154b9fd9badc07/greenlet-3.2.5-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2acb30e77042f747ca81f0a10cc153296567e92e666c5e1b117f4595afd43352", size = 278412, upload-time = "2026-02-20T20:03:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/8a3b0ed3cc34d8b988a44349437dfa0941f9c23ac108175f7b4ccea97111/greenlet-3.2.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:393c03c26c865f17f31d8db2f09603fadbe0581ad85a5d5908b131549fc38217", size = 644616, upload-time = "2026-02-20T20:30:44.823Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/2627bea183554695016af6cae93d7474fa90f61e5a6601a84ae7841cb720/greenlet-3.2.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:04e6a202cde56043fd355fefd1552c4caa5c087528121871d950eb4f1b51fa99", size = 658813, upload-time = "2026-02-20T20:37:26.255Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/75a5aeff487a26ba427a3837da6372f1fe6f2a9c6b2898e28ac99d491c11/greenlet-3.2.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45fcea7b697b91290b36eafc12fff479aca6ba6500d98ef6f34d5634c7119cbe", size = 655426, upload-time = "2026-02-20T20:07:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/53/91/9b5dfb4f3c88f8247c7a8f4c3759f0740bfa6bb0c59a9f6bf938e913df56/greenlet-3.2.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96e2bb8a56b7e1aed1dbfbbe0050cb2ecca99c7c91892fd1771e3afab63b3e3", size = 611138, upload-time = "2026-02-20T20:07:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8d/d0b086410512d9859c84e9242a9b341de9f5566011ddf3a3f6886b842b61/greenlet-3.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d7456e67b0be653dfe643bb37d9566cd30939c80f858e2ce6d2d54951f75b14a", size = 1126896, upload-time = "2026-02-20T20:32:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/ef/37/59fe12fe456e84ced6ba71781e28cde52a3124d1dd2077bc1727021f49fd/greenlet-3.2.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5ceb29d1f74c7280befbbfa27b9bf91ba4a07a1a00b2179a5d953fc219b16c42", size = 1154779, upload-time = "2026-02-20T20:06:27.583Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/d5d332fb73affaf7a1fbe80e49c2c7eae4f17c645af24a3b3fa25736d6f0/greenlet-3.2.5-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:f2cc88b50b9006b324c1b9f5f3552f9d4564c78af57cdfb4c7baf4f0aa089146", size = 277166, upload-time = "2026-02-20T20:03:57.077Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/89458e20db5a4f1c64f9a0191561227e76d809941ca2d7529006d17d3450/greenlet-3.2.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e66872daffa360b2537170b73ad530f14fa31785b1bc78080125d92edf0a6def", size = 644674, upload-time = "2026-02-20T20:30:46.118Z" }, + { url = "https://files.pythonhosted.org/packages/90/f8/9962175d2f2eaa629a7fd7545abacc8c4deda3baa4e52c1526d2eb5f5546/greenlet-3.2.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c5445ddb7b586d870dad32ca9fc47c287d6022a528d194efdb8912093c5303ad", size = 658834, upload-time = "2026-02-20T20:37:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d7/826d0e080f0a7ad5ec47c8d143bbd3ca0887657bb806595fe2434d12938a/greenlet-3.2.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:752c896a8c976548faafe8a306d446c6a4c68d4fd24699b84d4393bd9ac69a8e", size = 655760, upload-time = "2026-02-20T20:07:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/33bd4c2f816be8c8e16f71740c4130adf3a66a3dd2ba29de72b9d8dd1096/greenlet-3.2.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499b809e7738c8af0ff9ac9d5dd821cb93f4293065a9237543217f0b252f950a", size = 614132, upload-time = "2026-02-20T20:08:00.351Z" }, + { url = "https://files.pythonhosted.org/packages/48/79/f3891dcfc59097474a53cc3c624f2f2465e431ab493bda043b8c873fb20a/greenlet-3.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2c7429f6e9cea7cbf2637d86d3db12806ba970f7f972fcab39d6b54b4457cbaf", size = 1125286, upload-time = "2026-02-20T20:32:54.032Z" }, + { url = "https://files.pythonhosted.org/packages/ca/47/212b47e6d2d7a04c4083db1af2fdd291bc8fe99b7e3571bfa560b65fc361/greenlet-3.2.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a5e4b25e855800fba17713020c5c33e0a4b7a1829027719344f0c7c8870092a2", size = 1152825, upload-time = "2026-02-20T20:06:29Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9d/4e9b941be05f8da7ba804c6413761d2c11cca05994cbf0a015bd729419f0/greenlet-3.2.5-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:7123b29e6bad2f3f89681be4ef316480fca798ebe8d22fbaced9cc3775007a4f", size = 277627, upload-time = "2026-02-20T20:06:04.798Z" }, + { url = "https://files.pythonhosted.org/packages/23/cb/a73625c9a35138330014ecf3740c0d62e0c2b5e7279bb7f2586b1b199fac/greenlet-3.2.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e8fe0c72603201a86b2e038daf9b6c8570715f8779566419cff543b6ace88de", size = 690001, upload-time = "2026-02-20T20:30:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/83/49/6d1531109507bce7dfb23acf57a87013627ed3ac058851176e443a6a9134/greenlet-3.2.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:050703a60603db0e817364d69e048c70af299040c13a7e67792b9e62d4571196", size = 702953, upload-time = "2026-02-20T20:37:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/f7/38/f958ee90fab93529b30cc1e4a59b27c1112b640570043a84af84da3b3b98/greenlet-3.2.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6712bfd520530eb67331813f7112d3ee18e206f48b3d026d8a96cd2d2ad20251", size = 698995, upload-time = "2026-02-20T20:07:22.663Z" }, + { url = "https://files.pythonhosted.org/packages/51/c1/a603906e79716d61f08afedaf8aed62017661457aef233d62d6e57ecd511/greenlet-3.2.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bc06a78fa3ffbe2a75f1ebc7e040eacf6fa1050a9432953ab111fbbbf0d03c1", size = 661175, upload-time = "2026-02-20T20:08:01.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8f/f880ff4587d236b4d06893fb34da6b299aa0d00f6c8259673f80e1b6d63c/greenlet-3.2.5-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:dbe0e81e24982bb45907ca20152b31c2e3300ca352fdc4acbd4956e4a2cbc195", size = 274946, upload-time = "2026-02-20T20:05:21.979Z" }, + { url = "https://files.pythonhosted.org/packages/3c/50/f6c78b8420187fdfe97fcf2e6d1dd243a7742d272c32fd4d4b1095474b37/greenlet-3.2.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:15871afc0d78ec87d15d8412b337f287fc69f8f669346e391585824970931c48", size = 631781, upload-time = "2026-02-20T20:30:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/26/d6/3277f92e1961e6e9f41d9f173ea74b5c1f7065072637669f761626f26cc0/greenlet-3.2.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5bf0d7d62e356ef2e87e55e46a4e930ac165f9372760fb983b5631bb479e9d3a", size = 643740, upload-time = "2026-02-20T20:37:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6a/4f79d2e7b5ef3723fc5ffea0d6cb22627e5f95e0f19c973fa12bf1cf7891/greenlet-3.2.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6dff6433742073e5b6ad40953a78a0e8cddcb3f6869e5ea635d29a810ca5e7d0", size = 638382, upload-time = "2026-02-20T20:07:23.883Z" }, + { url = "https://files.pythonhosted.org/packages/4d/59/7aadf33f23c65dbf4db27e7f5b60c414797a61e954352ae4a86c5c8b0553/greenlet-3.2.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdd67619cefe1cc9fcab57c8853d2bb36eca9f166c0058cc0d428d471f7c785c", size = 587516, upload-time = "2026-02-20T20:08:02.841Z" }, + { url = "https://files.pythonhosted.org/packages/1d/46/b3422959f830de28a4eea447414e6bd7b980d755892f66ab52ad805da1c4/greenlet-3.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3828b309dfb1f117fe54867512a8265d8d4f00f8de6908eef9b885f4d8789062", size = 1115818, upload-time = "2026-02-20T20:32:55.786Z" }, + { url = "https://files.pythonhosted.org/packages/54/4a/3d1c9728f093415637cf3696909fa10852632e33e68238fb8ca60eb90de1/greenlet-3.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:67725ae9fea62c95cf1aa230f1b8d4dc38f7cd14f6103d1df8a5a95657eb8e54", size = 1140219, upload-time = "2026-02-20T20:06:30.334Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1326,57 +1821,54 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/94/8a28707adb00bed1bf22dac16ccafe60faf2ade353dcb32c3617ee917307/fonttools-4.61.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24", size = 2854799, upload-time = "2025-12-12T17:29:27.5Z" }, - { url = "https://files.pythonhosted.org/packages/94/93/c2e682faaa5ee92034818d8f8a8145ae73eb83619600495dcf8503fa7771/fonttools-4.61.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958", size = 2403032, upload-time = "2025-12-12T17:29:30.115Z" }, - { url = "https://files.pythonhosted.org/packages/f1/62/1748f7e7e1ee41aa52279fd2e3a6d0733dc42a673b16932bad8e5d0c8b28/fonttools-4.61.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da", size = 4897863, upload-time = "2025-12-12T17:29:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/69/69/4ca02ee367d2c98edcaeb83fc278d20972502ee071214ad9d8ca85e06080/fonttools-4.61.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6", size = 4859076, upload-time = "2025-12-12T17:29:34.907Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f5/660f9e3cefa078861a7f099107c6d203b568a6227eef163dd173bfc56bdc/fonttools-4.61.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1", size = 4875623, upload-time = "2025-12-12T17:29:37.33Z" }, - { url = "https://files.pythonhosted.org/packages/63/d1/9d7c5091d2276ed47795c131c1bf9316c3c1ab2789c22e2f59e0572ccd38/fonttools-4.61.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881", size = 4993327, upload-time = "2025-12-12T17:29:39.781Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2d/28def73837885ae32260d07660a052b99f0aa00454867d33745dfe49dbf0/fonttools-4.61.1-cp310-cp310-win32.whl", hash = "sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47", size = 1502180, upload-time = "2025-12-12T17:29:42.217Z" }, - { url = "https://files.pythonhosted.org/packages/63/fa/bfdc98abb4dd2bd491033e85e3ba69a2313c850e759a6daa014bc9433b0f/fonttools-4.61.1-cp310-cp310-win_amd64.whl", hash = "sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6", size = 1550654, upload-time = "2025-12-12T17:29:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, - { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, - { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, - { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, - { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, - { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, - { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, - { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, - { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, - { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, - { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/03/84359833f7e1d49a883e92777637c592306030e30cee5e2b1e6476f95c88/greenlet-3.5.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a", size = 283502, upload-time = "2026-04-27T12:20:55.213Z" }, + { url = "https://files.pythonhosted.org/packages/25/ce/6f9f008266273aa14a2e011945797ac5802b97b8b40efe7afe1ee6c1afc9/greenlet-3.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f", size = 600508, upload-time = "2026-04-27T12:52:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/b0f3272c2368ea2c1aa19a5ad70db0be8f8dff6e6d3d1eb82efa00cbcf19/greenlet-3.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb", size = 613283, upload-time = "2026-04-27T12:59:37.957Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ac/0b509b6fb93551ce5a01612ee1acda7f7dda4bbb66c99aeb2ab403d205dc/greenlet-3.5.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb", size = 613418, upload-time = "2026-04-27T12:25:23.852Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/2b2b680ec87aaa97998fb5b8d76658d4d3560386864f17efab33ba7c2e24/greenlet-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977", size = 1572229, upload-time = "2026-04-27T12:53:23.509Z" }, + { url = "https://files.pythonhosted.org/packages/61/e4/42b259e7a19aff1a270a4bd82caf6353109ed6860c9454e18f37162b83ae/greenlet-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0", size = 1639886, upload-time = "2026-04-27T12:25:22.325Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b4/733ca47b883b67c57f90d3ecb21055c9ec753597d10754ac201644061f9d/greenlet-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858", size = 237795, upload-time = "2026-04-27T12:21:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0f/a91f143f356523ff682309732b175765a9bc2836fd7c081c2c67fedc1ad4/greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082", size = 284726, upload-time = "2026-04-27T12:20:51.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/82/800646c7ffc5dbabd75ddd2f6b519bb898c0c9c969e5d0473bfe5d20bcce/greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3", size = 604264, upload-time = "2026-04-27T12:52:39.494Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/354867c0bba812fc33b15bc55aedafedd0aee3c7dd91dfca22444157dc0c/greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c", size = 616099, upload-time = "2026-04-27T12:59:39.623Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/815bece7399e01cadb69014219eebd0042339875c59a59b0820a46ece356/greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662", size = 615198, upload-time = "2026-04-27T12:25:25.928Z" }, + { url = "https://files.pythonhosted.org/packages/10/80/3b2c0a895d6698f6ddb31b07942ebfa982f3e30888bc5546a5b5990de8b2/greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b", size = 1574927, upload-time = "2026-04-27T12:53:25.81Z" }, + { url = "https://files.pythonhosted.org/packages/44/0e/f354af514a4c61454dbc68e44d47544a5a4d6317e30b77ddfa3a09f4c5f3/greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4", size = 1642683, upload-time = "2026-04-27T12:25:23.9Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6a/87f38255201e993a1915265ebb80cd7c2c78b04a45744995abbf6b259fd8/greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8", size = 238115, upload-time = "2026-04-27T12:21:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f8/450fe3c5938fa737ea4d22699772e6e34e8e24431a47bf4e8a1ceed4a98e/greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339", size = 235017, upload-time = "2026-04-27T12:22:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, + { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, + { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, + { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, + { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, + { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, + { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, + { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, ] [[package]] @@ -1384,7 +1876,8 @@ name = "h5py" version = "3.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -1420,12 +1913,15 @@ wheels = [ [[package]] name = "h5py" -version = "3.15.1" +version = "3.16.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1436,49 +1932,57 @@ resolution-markers = [ ] dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/30/8fa61698b438dd751fa46a359792e801191dadab560d0a5f1c709443ef8e/h5py-3.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67e59f6c2f19a32973a40f43d9a088ae324fe228c8366e25ebc57ceebf093a6b", size = 3414477, upload-time = "2025-10-16T10:33:24.201Z" }, - { url = "https://files.pythonhosted.org/packages/16/16/db2f63302937337c4e9e51d97a5984b769bdb7488e3d37632a6ac297f8ef/h5py-3.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e2f471688402c3404fa4e13466e373e622fd4b74b47b56cfdff7cc688209422", size = 2850298, upload-time = "2025-10-16T10:33:27.747Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2e/f1bb7de9b05112bfd14d5206090f0f92f1e75bbb412fbec5d4653c3d44dd/h5py-3.15.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c45802bcb711e128a6839cb6c01e9ac648dc55df045c9542a675c771f15c8d5", size = 4523605, upload-time = "2025-10-16T10:33:31.168Z" }, - { url = "https://files.pythonhosted.org/packages/05/8a/63f4b08f3628171ce8da1a04681a65ee7ac338fde3cb3e9e3c9f7818e4da/h5py-3.15.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64ce3f6470adb87c06e3a8dd1b90e973699f1759ad79bfa70c230939bff356c9", size = 4735346, upload-time = "2025-10-16T10:33:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/74/48/f16d12d9de22277605bcc11c0dcab5e35f06a54be4798faa2636b5d44b3c/h5py-3.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4411c1867b9899a25e983fff56d820a66f52ac326bbe10c7cdf7d832c9dcd883", size = 4175305, upload-time = "2025-10-16T10:33:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2f/47cdbff65b2ce53c27458c6df63a232d7bb1644b97df37b2342442342c84/h5py-3.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2cbc4104d3d4aca9d6db8c0c694555e255805bfeacf9eb1349bda871e26cacbe", size = 4653602, upload-time = "2025-10-16T10:33:42.188Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/dc08de359c2f43a67baa529cb70d7f9599848750031975eed92d6ae78e1d/h5py-3.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:01f55111ca516f5568ae7a7fc8247dfce607de331b4467ee8a9a6ed14e5422c7", size = 2873601, upload-time = "2025-10-16T10:33:45.323Z" }, - { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, - { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, - { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, - { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, - { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, - { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, - { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, - { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, - { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, - { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, - { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, - { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, - { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, - { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, - { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +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]] @@ -1486,7 +1990,8 @@ name = "id" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "urllib3" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } wheels = [ @@ -1495,34 +2000,93 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] name = "imagesize" -version = "1.4.1" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/59/4b0dd64676aa6fb4986a755790cb6fc558559cf0084effad516820208ec3/imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f", size = 1281127, upload-time = "2026-03-03T01:59:54.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b1/a0662b03103c66cf77101a187f396ea91167cd9b7d5d3a2e465ad2c7ee9b/imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899", size = 5763, upload-time = "2026-03-03T01:59:52.343Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +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 = "importlib-metadata" version = "8.7.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "zipp", marker = "python_full_version >= '3.10'" }, +] +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 = "importlib-resources" version = "6.5.2" @@ -1540,7 +2104,8 @@ name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ @@ -1552,9 +2117,12 @@ name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1582,7 +2150,8 @@ name = "jaraco-classes" version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools" }, + { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "more-itertools", version = "11.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ @@ -1591,28 +2160,90 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.1.0" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/7b/c3081ff1af947915503121c649f26a778e1a2101fd525f74aef997d75b7e/jaraco_context-6.1.1.tar.gz", hash = "sha256:bc046b2dc94f1e5532bd02402684414575cc11f565d929b6563125deb0a6e581", size = 15832, upload-time = "2026-03-07T15:46:04.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl", hash = "sha256:0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808", size = 7005, upload-time = "2026-03-07T15:46:03.515Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] dependencies = [ - { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, + { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, ] [[package]] name = "jaraco-functools" version = "4.4.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] dependencies = [ - { name = "more-itertools" }, + { name = "more-itertools", version = "10.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, ] +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "more-itertools", version = "11.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, +] + [[package]] name = "jeepney" version = "0.9.0" @@ -1636,66 +2267,73 @@ wheels = [ [[package]] name = "kalign-python" -version = "3.5.1" +version = "3.5.2" source = { editable = "." } dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] [package.optional-dependencies] all = [ { name = "biopython", version = "1.85", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "biopython", version = "1.86", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "biopython", version = "1.87", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "scikit-bio", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "scikit-bio", version = "0.7.1.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scikit-bio", version = "0.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "seaborn" }, ] analysis = [ { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "seaborn" }, ] benchmark = [ { name = "dash" }, + { name = "kneed" }, + { name = "optuna" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "plotly" }, + { name = "pymoo", version = "0.6.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pymoo", version = "0.6.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rich" }, { name = "tqdm" }, ] biopython = [ { name = "biopython", version = "1.85", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "biopython", version = "1.86", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "biopython", version = "1.87", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] dev = [ { name = "biopython", version = "1.85", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "biopython", version = "1.86", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "biopython", version = "1.87", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "black", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "build" }, + { name = "black", version = "26.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "build", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "build", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "flake8" }, - { name = "mypy" }, + { name = "mypy", version = "1.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mypy", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, { name = "rich" }, { name = "scikit-bio", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "scikit-bio", version = "0.7.1.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scikit-bio", version = "0.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "twine" }, ] docs = [ { name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "myst-parser", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "myst-parser", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, @@ -1704,15 +2342,15 @@ docs = [ ] io = [ { name = "biopython", version = "1.85", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "biopython", version = "1.86", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "biopython", version = "1.87", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] skbio = [ { name = "scikit-bio", version = "0.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "scikit-bio", version = "0.7.1.post1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "scikit-bio", version = "0.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] test = [ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, @@ -1721,9 +2359,10 @@ test = [ [package.dev-dependencies] dev = [ - { name = "build" }, + { name = "build", version = "1.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "build", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-benchmark" }, ] @@ -1737,15 +2376,18 @@ requires-dist = [ { name = "build", marker = "extra == 'dev'" }, { name = "dash", marker = "extra == 'benchmark'", specifier = ">=2.14" }, { name = "flake8", marker = "extra == 'dev'" }, + { name = "kneed", marker = "extra == 'benchmark'", specifier = ">=0.8" }, { name = "matplotlib", marker = "extra == 'all'", specifier = ">=3.9.4" }, { name = "matplotlib", marker = "extra == 'analysis'", specifier = ">=3.9.4" }, { name = "mypy", marker = "extra == 'dev'" }, { name = "myst-parser", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=1.19.0" }, + { name = "optuna", marker = "extra == 'benchmark'", specifier = ">=3.0" }, { name = "pandas", marker = "extra == 'all'", specifier = ">=2.3.0" }, { name = "pandas", marker = "extra == 'analysis'", specifier = ">=2.3.0" }, { name = "pandas", marker = "extra == 'benchmark'", specifier = ">=2.0" }, { name = "plotly", marker = "extra == 'benchmark'", specifier = ">=5.18" }, + { name = "pymoo", marker = "extra == 'benchmark'", specifier = ">=0.6" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=6.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=6.0" }, { name = "pytest-benchmark", marker = "extra == 'dev'" }, @@ -1754,6 +2396,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'test'" }, { name = "pytest-xdist", marker = "extra == 'dev'" }, { name = "pytest-xdist", marker = "extra == 'test'" }, + { name = "rich", marker = "extra == 'benchmark'", specifier = ">=13.0" }, { name = "rich", marker = "extra == 'dev'" }, { name = "rich", marker = "extra == 'test'" }, { name = "scikit-bio", marker = "extra == 'all'", specifier = ">=0.6.3" }, @@ -1780,10 +2423,13 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "9.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, { name = "jaraco-classes" }, - { name = "jaraco-context" }, - { name = "jaraco-functools" }, + { name = "jaraco-context", version = "6.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jaraco-context", version = "6.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "jaraco-functools", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "jaraco-functools", version = "4.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "jeepney", marker = "sys_platform == 'linux'" }, { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", version = "3.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and sys_platform == 'linux'" }, @@ -1799,7 +2445,8 @@ name = "kiwisolver" version = "1.4.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286, upload-time = "2024-09-04T09:39:44.302Z" } wheels = [ @@ -1899,12 +2546,15 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1913,191 +2563,250 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5d/8ce64e36d4e3aac5ca96996457dcf33e34e6051492399a3f1fec5657f30b/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", size = 124159, upload-time = "2025-08-10T21:25:35.472Z" }, - { url = "https://files.pythonhosted.org/packages/96/1e/22f63ec454874378175a5f435d6ea1363dd33fb2af832c6643e4ccea0dc8/kiwisolver-1.4.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", size = 66578, upload-time = "2025-08-10T21:25:36.73Z" }, - { url = "https://files.pythonhosted.org/packages/41/4c/1925dcfff47a02d465121967b95151c82d11027d5ec5242771e580e731bd/kiwisolver-1.4.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", size = 65312, upload-time = "2025-08-10T21:25:37.658Z" }, - { url = "https://files.pythonhosted.org/packages/d4/42/0f333164e6307a0687d1eb9ad256215aae2f4bd5d28f4653d6cd319a3ba3/kiwisolver-1.4.9-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", size = 1628458, upload-time = "2025-08-10T21:25:39.067Z" }, - { url = "https://files.pythonhosted.org/packages/86/b6/2dccb977d651943995a90bfe3495c2ab2ba5cd77093d9f2318a20c9a6f59/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", size = 1225640, upload-time = "2025-08-10T21:25:40.489Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/362ebd3eec46c850ccf2bfe3e30f2fc4c008750011f38a850f088c56a1c6/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", size = 1244074, upload-time = "2025-08-10T21:25:42.221Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bb/f09a1e66dab8984773d13184a10a29fe67125337649d26bdef547024ed6b/kiwisolver-1.4.9-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", size = 1293036, upload-time = "2025-08-10T21:25:43.801Z" }, - { url = "https://files.pythonhosted.org/packages/ea/01/11ecf892f201cafda0f68fa59212edaea93e96c37884b747c181303fccd1/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", size = 2175310, upload-time = "2025-08-10T21:25:45.045Z" }, - { url = "https://files.pythonhosted.org/packages/7f/5f/bfe11d5b934f500cc004314819ea92427e6e5462706a498c1d4fc052e08f/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220", size = 2270943, upload-time = "2025-08-10T21:25:46.393Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/259f786bf71f1e03e73d87e2db1a9a3bcab64d7b4fd780167123161630ad/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", size = 2440488, upload-time = "2025-08-10T21:25:48.074Z" }, - { url = "https://files.pythonhosted.org/packages/1b/76/c989c278faf037c4d3421ec07a5c452cd3e09545d6dae7f87c15f54e4edf/kiwisolver-1.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", size = 2246787, upload-time = "2025-08-10T21:25:49.442Z" }, - { url = "https://files.pythonhosted.org/packages/a2/55/c2898d84ca440852e560ca9f2a0d28e6e931ac0849b896d77231929900e7/kiwisolver-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", size = 73730, upload-time = "2025-08-10T21:25:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/e8/09/486d6ac523dd33b80b368247f238125d027964cfacb45c654841e88fb2ae/kiwisolver-1.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", size = 65036, upload-time = "2025-08-10T21:25:52.063Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a2/63/fde392691690f55b38d5dd7b3710f5353bf7a8e52de93a22968801ab8978/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", size = 60183, upload-time = "2025-08-10T21:27:37.669Z" }, - { url = "https://files.pythonhosted.org/packages/27/b1/6aad34edfdb7cced27f371866f211332bba215bfd918ad3322a58f480d8b/kiwisolver-1.4.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", size = 58675, upload-time = "2025-08-10T21:27:39.031Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1a/23d855a702bb35a76faed5ae2ba3de57d323f48b1f6b17ee2176c4849463/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", size = 80277, upload-time = "2025-08-10T21:27:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5b/5239e3c2b8fb5afa1e8508f721bb77325f740ab6994d963e61b2b7abcc1e/kiwisolver-1.4.9-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", size = 77994, upload-time = "2025-08-10T21:27:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1c/5d4d468fb16f8410e596ed0eac02d2c68752aa7dc92997fe9d60a7147665/kiwisolver-1.4.9-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", size = 73744, upload-time = "2025-08-10T21:27:42.254Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +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 = "kneed" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/64/4bb8f8a7a4627b585a66d5bec0c9b30ae5b39a4caea1775c8bfb3fb3f4cf/kneed-0.8.6.tar.gz", hash = "sha256:65b22727c623661701f15edf057f2e6c73e2b1ad4e68cd9ca4291675c318b5ef", size = 13161, upload-time = "2026-03-20T21:01:51.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/cd/23c89d53c36028bccb39f55aa5dd24c4bdaab76c4d556ad43dc8cf026918/kneed-0.8.6-py3-none-any.whl", hash = "sha256:3412e7b70bce07717386d24fab37f0f985968d1b85ea0c749a6b98caccaf65ec", size = 10797, upload-time = "2026-03-20T21:01:50.87Z" }, ] [[package]] name = "librt" -version = "0.7.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/13/57b06758a13550c5f09563893b004f98e9537ee6ec67b7df85c3571c8832/librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d", size = 56521, upload-time = "2026-01-14T12:54:40.066Z" }, - { url = "https://files.pythonhosted.org/packages/c2/24/bbea34d1452a10612fb45ac8356f95351ba40c2517e429602160a49d1fd0/librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b", size = 58456, upload-time = "2026-01-14T12:54:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/04/72/a168808f92253ec3a810beb1eceebc465701197dbc7e865a1c9ceb3c22c7/librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d", size = 164392, upload-time = "2026-01-14T12:54:42.843Z" }, - { url = "https://files.pythonhosted.org/packages/14/5c/4c0d406f1b02735c2e7af8ff1ff03a6577b1369b91aa934a9fa2cc42c7ce/librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d", size = 172959, upload-time = "2026-01-14T12:54:44.602Z" }, - { url = "https://files.pythonhosted.org/packages/82/5f/3e85351c523f73ad8d938989e9a58c7f59fb9c17f761b9981b43f0025ce7/librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c", size = 186717, upload-time = "2026-01-14T12:54:45.986Z" }, - { url = "https://files.pythonhosted.org/packages/08/f8/18bfe092e402d00fe00d33aa1e01dda1bd583ca100b393b4373847eade6d/librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c", size = 184585, upload-time = "2026-01-14T12:54:47.139Z" }, - { url = "https://files.pythonhosted.org/packages/4e/fc/f43972ff56fd790a9fa55028a52ccea1875100edbb856b705bd393b601e3/librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d", size = 180497, upload-time = "2026-01-14T12:54:48.946Z" }, - { url = "https://files.pythonhosted.org/packages/e1/3a/25e36030315a410d3ad0b7d0f19f5f188e88d1613d7d3fd8150523ea1093/librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0", size = 200052, upload-time = "2026-01-14T12:54:50.382Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b8/f3a5a1931ae2a6ad92bf6893b9ef44325b88641d58723529e2c2935e8abe/librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85", size = 43477, upload-time = "2026-01-14T12:54:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/fe/91/c4202779366bc19f871b4ad25db10fcfa1e313c7893feb942f32668e8597/librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c", size = 49806, upload-time = "2026-01-14T12:54:53.149Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, - { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, - { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, - { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, - { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, - { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, - { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, - { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, - { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, - { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, - { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, - { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, - { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, - { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, - { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, - { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, - { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, - { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, - { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, - { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, - { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, - { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, - { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, - { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, - { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, - { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, - { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, - { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, - { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, - { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9b/2668bb01f568bc89ace53736df950845f8adfcacdf6da087d5cef12110cb/librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6", size = 56680, upload-time = "2026-01-14T12:56:02.602Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d4/dbb3edf2d0ec4ba08dcaf1865833d32737ad208962d4463c022cea6e9d3c/librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b", size = 58612, upload-time = "2026-01-14T12:56:03.616Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/64b029de4ac9901fcd47832c650a0fd050555a452bd455ce8deddddfbb9f/librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c", size = 163654, upload-time = "2026-01-14T12:56:04.975Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/95e2abb1b48eb8f8c7fc2ae945321a6b82777947eb544cc785c3f37165b2/librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5", size = 172477, upload-time = "2026-01-14T12:56:06.103Z" }, - { url = "https://files.pythonhosted.org/packages/7e/27/9bdf12e05b0eb089dd008d9c8aabc05748aad9d40458ade5e627c9538158/librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71", size = 186220, upload-time = "2026-01-14T12:56:09.958Z" }, - { url = "https://files.pythonhosted.org/packages/53/6a/c3774f4cc95e68ed444a39f2c8bd383fd18673db7d6b98cfa709f6634b93/librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e", size = 183841, upload-time = "2026-01-14T12:56:11.109Z" }, - { url = "https://files.pythonhosted.org/packages/58/6b/48702c61cf83e9c04ad5cec8cad7e5e22a2cde23a13db8ef341598897ddd/librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63", size = 179751, upload-time = "2026-01-14T12:56:12.278Z" }, - { url = "https://files.pythonhosted.org/packages/35/87/5f607fc73a131d4753f4db948833063c6aad18e18a4e6fbf64316c37ae65/librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94", size = 199319, upload-time = "2026-01-14T12:56:13.425Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cc/b7c5ac28ae0f0645a9681248bae4ede665bba15d6f761c291853c5c5b78e/librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb", size = 43434, upload-time = "2026-01-14T12:56:14.781Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5d/dce0c92f786495adf2c1e6784d9c50a52fb7feb1cfb17af97a08281a6e82/librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be", size = 49801, upload-time = "2026-01-14T12:56:15.827Z" }, +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/10/37fd9e9ba96cb0bd742dfb20fc3d082e54bdbec759d7300df927f360ef07/librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", size = 141706, upload-time = "2026-05-10T18:15:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/1b1466f358e4a0b728051f69bc27e67b432c6eaa2e05b88db49d3785ae0d/librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", size = 142605, upload-time = "2026-05-10T18:15:18.148Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/ed26dd2f6bc9a0baf48306433e579e8d354d70b2bcb78134ed950a5d0e1e/librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", size = 476555, upload-time = "2026-05-10T18:15:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/11891191c0e0a3fd617724e891f6e67a71a7658974a892b9a9a97fdb2977/librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", size = 468434, upload-time = "2026-05-10T18:15:20.87Z" }, + { url = "https://files.pythonhosted.org/packages/6f/50/5ec949d7f9ce1a07af903aa3e13abb98b717923bdead6e719b2f824ccc07/librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", size = 496918, upload-time = "2026-05-10T18:15:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c4/177336c7524e34875a38bf668e88b193a6723a4eb4045d07f74df6e1506c/librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", size = 490334, upload-time = "2026-05-10T18:15:24.2Z" }, + { url = "https://files.pythonhosted.org/packages/13/1f/da3112f7569eda3b49f9a2629bae1fe059812b6085df16c885f6454dff49/librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", size = 511287, upload-time = "2026-05-10T18:15:26.226Z" }, + { url = "https://files.pythonhosted.org/packages/fa/94/03fec301522e172d105581431223be56b27594ff46440ebfbb658a3735d5/librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", size = 517202, upload-time = "2026-05-10T18:15:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6e/339f6e5a7b413ce014f1917a756dae630fe59cc99f34153205b1cb540901/librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", size = 497517, upload-time = "2026-05-10T18:15:29.614Z" }, + { url = "https://files.pythonhosted.org/packages/cd/43/acdd5ce317cb46e8253ca9bfbdb8b12e68a24d745949336a7f3d5fb79ba0/librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", size = 538878, upload-time = "2026-05-10T18:15:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/29/b5/7a25bb12e3172839f647f196b3e988318b7bb1ca7501732a225c4dce2ec0/librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", size = 100070, upload-time = "2026-05-10T18:15:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0d/ebbcf4d77999c02c937b05d2b90ff4cd4dcc7e9a365ba132329ac1fe7a0f/librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", size = 117918, upload-time = "2026-05-10T18:15:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, + { url = "https://files.pythonhosted.org/packages/66/54/5d5f27cc840d2d8a64d60e0650dba14044a95d85a875e42af2eb104ac8b9/librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f", size = 142475, upload-time = "2026-05-10T18:17:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/f9/72/535efe79cf47f70975e0b14ceb3b7984bb7e8b97fb2867d3979771be0b6a/librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677", size = 143365, upload-time = "2026-05-10T18:17:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/83/cc/4130d462aeaf190357517d2a48a0a25030fbfd604230f6c45908452fff9c/librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab", size = 475743, upload-time = "2026-05-10T18:17:10.822Z" }, + { url = "https://files.pythonhosted.org/packages/62/e8/3c8000edefeb443fd2139692fb966f6c5556cb1032c44f734550896df3b9/librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0", size = 467088, upload-time = "2026-05-10T18:17:12.273Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a1/6de754256493924874e5fa6c0f4f990d8b101c38d974589020d9dc3d02af/librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1", size = 496277, upload-time = "2026-05-10T18:17:13.662Z" }, + { url = "https://files.pythonhosted.org/packages/92/fb/c34cb5358d6f993f85014045decd6dccd089a6f11d188660e062ee6262ff/librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e", size = 489320, upload-time = "2026-05-10T18:17:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/7761d70841bac875be9627496546b2eccbdeb07da3e42431bc4a40cf0819/librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa", size = 510221, upload-time = "2026-05-10T18:17:16.595Z" }, + { url = "https://files.pythonhosted.org/packages/cb/8d/af9d4ac1057cd4e472b89553924b528b3d34afa6b7167645b7e6db39596b/librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1", size = 516650, upload-time = "2026-05-10T18:17:18.245Z" }, + { url = "https://files.pythonhosted.org/packages/86/f4/08faaf48ce0833d3717ebe0a0054c09a05df1bc83ee2715113c9901cc147/librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5", size = 496622, upload-time = "2026-05-10T18:17:19.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/11/ec3e390627f70477093909875a38843c826ee2ff554d1649645c7cc59248/librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192", size = 538049, upload-time = "2026-05-10T18:17:21.221Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a7/649401dae7ea8645dd218aa2d9c351afa7b9e0645f07dc8776a1972c0cad/librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f", size = 100360, upload-time = "2026-05-10T18:17:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7e/6a9711d78f338445e36992a90071962294f5bab388b554ef8a313e6412dd/librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3", size = 118407, upload-time = "2026-05-10T18:17:24.01Z" }, +] + +[[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]] @@ -2106,7 +2815,8 @@ version = "3.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "mdurl", marker = "python_full_version < '3.11'" }, @@ -2118,12 +2828,15 @@ wheels = [ [[package]] name = "markdown-it-py" -version = "4.0.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2134,9 +2847,9 @@ resolution-markers = [ dependencies = [ { name = "mdurl", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +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/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { 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]] @@ -2240,7 +2953,8 @@ name = "matplotlib" version = "3.9.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -2300,12 +3014,15 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.8" +version = "3.10.9" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2318,71 +3035,71 @@ dependencies = [ { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "cycler", marker = "python_full_version >= '3.10'" }, - { name = "fonttools", version = "4.61.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "kiwisolver", version = "1.4.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fonttools", version = "4.63.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "kiwisolver", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "12.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyparsing", marker = "python_full_version >= '3.10'" }, { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, - { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, - { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +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]] @@ -2399,7 +3116,8 @@ name = "mdit-py-plugins" version = "0.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -2411,12 +3129,15 @@ wheels = [ [[package]] name = "mdit-py-plugins" -version = "0.5.0" +version = "0.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2427,11 +3148,11 @@ resolution-markers = [ ] dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, ] [[package]] @@ -2443,25 +3164,78 @@ 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 = "moocore" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "platformdirs", version = "4.9.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/95/0e36dbb8f44690a3540ca3c8c08f9000799d95a5eeec2a49abc7c76a8a0f/moocore-0.3.1.tar.gz", hash = "sha256:a8f83cfbc0aa81c1c9dd33e473adbc9b638dc3b1a6943753f8146770bb76bae4", size = 424672, upload-time = "2026-05-04T21:39:12.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/fd/da7e96fca6a05f5a80b682aa75f301e2ce870836fc444973a2c717beb945/moocore-0.3.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cf22852e951e66cb1145858ff1e4fabcb2810dfc2c19d8df99bdc1a52b28c591", size = 631735, upload-time = "2026-05-04T21:39:01.39Z" }, + { url = "https://files.pythonhosted.org/packages/40/df/c4715242db5a0d0e17578c5e643334c17d7760cd9f66c17b76a3b8267fb5/moocore-0.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14af334b83ddf6fbdec8e5ab6e3246e85ad6b34b0f1b2c44a52e3c5561ff5530", size = 882475, upload-time = "2026-05-04T21:39:03.675Z" }, + { url = "https://files.pythonhosted.org/packages/34/74/6230c8c2865586ce4c4b0eea994bccadd9e3e28f6acd9cade123ba8516fa/moocore-0.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd0a65e501e704e5270d564c3b32438ddac34d22d3c5c54b01b8c897f4d949aa", size = 866948, upload-time = "2026-05-04T21:39:05.2Z" }, + { url = "https://files.pythonhosted.org/packages/23/10/6cbbc1039ce33a862d6fa7e24d381635ffd183a3e5a2036bad8477cb9c62/moocore-0.3.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3fd980c39e8ad4ba12147ac01f700ec402fb25d452beeccc1a7dc2e0b61e95ca", size = 875147, upload-time = "2026-05-04T21:39:06.846Z" }, + { url = "https://files.pythonhosted.org/packages/14/17/0ead0e1ea08fdc9a980c2525c61e034d5161675cbb320eae61028e93868d/moocore-0.3.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7b9ddc86e6949d5b5466e805d474b5ae2dbb866ae218dfe7b53cee9eed0151ff", size = 862014, upload-time = "2026-05-04T21:39:08.232Z" }, + { url = "https://files.pythonhosted.org/packages/09/93/a21b67ddaddb89843869b78e7691d20eab5c5b869a2908cda86fd0d8f663/moocore-0.3.1-cp310-abi3-win_amd64.whl", hash = "sha256:0d5b666bf80bc972816ba8586de77ac6eeaf2b55ab493310487c14e90d491650", size = 517463, upload-time = "2026-05-04T21:39:09.574Z" }, + { url = "https://files.pythonhosted.org/packages/c5/dc/cd6f4a8977011db9a62c7981462e1820593ee44720a1f3c9a7abe8a94421/moocore-0.3.1-cp310-abi3-win_arm64.whl", hash = "sha256:52178eaaa12babe9532c5494d619cb44c791660adc7ae70dbd2daccf37b7990f", size = 505777, upload-time = "2026-05-04T21:39:11.206Z" }, +] + [[package]] name = "more-itertools" version = "10.8.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] +[[package]] +name = "more-itertools" +version = "11.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, +] + [[package]] name = "mypy" version = "1.19.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] dependencies = [ - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, + { name = "librt", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions", marker = "python_full_version < '3.10'" }, + { name = "pathspec", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ @@ -2504,6 +3278,80 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "ast-serialize", marker = "python_full_version >= '3.10'" }, + { name = "librt", marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, + { name = "pathspec", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" }, + { url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" }, + { url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" }, + { url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -2518,7 +3366,8 @@ name = "myst-parser" version = "3.0.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -2544,7 +3393,7 @@ dependencies = [ { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "jinja2", marker = "python_full_version == '3.10.*'" }, { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "mdit-py-plugins", version = "0.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "mdit-py-plugins", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "pyyaml", marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, ] @@ -2555,12 +3404,15 @@ wheels = [ [[package]] name = "myst-parser" -version = "5.0.0" +version = "5.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2571,24 +3423,24 @@ resolution-markers = [ dependencies = [ { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "mdit-py-plugins", version = "0.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mdit-py-plugins", version = "0.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyyaml", marker = "python_full_version >= '3.11'" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, + { url = "https://files.pythonhosted.org/packages/09/dc/f3dfb7488b770f3f67e6545085bf2abea5172e88f57b8ad25ef860ca704c/myst_parser-5.1.0-py3-none-any.whl", hash = "sha256:9c91c52b3cdb4d94a6506e4fab4e2f296c7623a0da0dcbe6de1565c3dad67a8a", size = 85817, upload-time = "2026-05-13T09:38:17.904Z" }, ] [[package]] name = "narwhals" -version = "2.16.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/6f/713be67779028d482c6e0f2dde5bc430021b2578a4808c1c9f6d7ad48257/narwhals-2.16.0.tar.gz", hash = "sha256:155bb45132b370941ba0396d123cf9ed192bf25f39c4cea726f2da422ca4e145", size = 618268, upload-time = "2026-02-02T10:31:00.545Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/0e/3ad61eb87088cc4932e0d851531fa82f845a6230b68b091a0e298cc7e537/narwhals-2.21.0.tar.gz", hash = "sha256:7c6e7f50528e62b7a967dd864d7e117d2955d38d4f730653ce46a9861358e2dc", size = 633083, upload-time = "2026-05-08T12:29:02.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e1/68c2256b69a314eba133673377ba9118c356f6342a0c02b61de449cf2bf2/narwhals-2.21.0-py3-none-any.whl", hash = "sha256:1e6617d0fca68ae1fda29e5397c4eaacd3ffc9fffe6bcd6ded0c690475e853be", size = 451943, upload-time = "2026-05-08T12:29:01.058Z" }, ] [[package]] @@ -2611,35 +3463,36 @@ wheels = [ [[package]] name = "nh3" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/a5/34c26015d3a434409f4d2a1cd8821a06c05238703f49283ffeb937bef093/nh3-0.3.2.tar.gz", hash = "sha256:f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376", size = 19288, upload-time = "2025-10-30T11:17:45.948Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/01/a1eda067c0ba823e5e2bb033864ae4854549e49fb6f3407d2da949106bfb/nh3-0.3.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d", size = 1419839, upload-time = "2025-10-30T11:17:09.956Z" }, - { url = "https://files.pythonhosted.org/packages/30/57/07826ff65d59e7e9cc789ef1dc405f660cabd7458a1864ab58aefa17411b/nh3-0.3.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130", size = 791183, upload-time = "2025-10-30T11:17:11.99Z" }, - { url = "https://files.pythonhosted.org/packages/af/2f/e8a86f861ad83f3bb5455f596d5c802e34fcdb8c53a489083a70fd301333/nh3-0.3.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b", size = 829127, upload-time = "2025-10-30T11:17:13.192Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/77aef4daf0479754e8e90c7f8f48f3b7b8725a3b8c0df45f2258017a6895/nh3-0.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5", size = 997131, upload-time = "2025-10-30T11:17:14.677Z" }, - { url = "https://files.pythonhosted.org/packages/41/ee/fd8140e4df9d52143e89951dd0d797f5546004c6043285289fbbe3112293/nh3-0.3.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31", size = 1068783, upload-time = "2025-10-30T11:17:15.861Z" }, - { url = "https://files.pythonhosted.org/packages/87/64/bdd9631779e2d588b08391f7555828f352e7f6427889daf2fa424bfc90c9/nh3-0.3.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99", size = 994732, upload-time = "2025-10-30T11:17:17.155Z" }, - { url = "https://files.pythonhosted.org/packages/79/66/90190033654f1f28ca98e3d76b8be1194505583f9426b0dcde782a3970a2/nh3-0.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868", size = 975997, upload-time = "2025-10-30T11:17:18.77Z" }, - { url = "https://files.pythonhosted.org/packages/34/30/ebf8e2e8d71fdb5a5d5d8836207177aed1682df819cbde7f42f16898946c/nh3-0.3.2-cp314-cp314t-win32.whl", hash = "sha256:1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93", size = 583364, upload-time = "2025-10-30T11:17:20.286Z" }, - { url = "https://files.pythonhosted.org/packages/94/ae/95c52b5a75da429f11ca8902c2128f64daafdc77758d370e4cc310ecda55/nh3-0.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13", size = 589982, upload-time = "2025-10-30T11:17:21.384Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bd/c7d862a4381b95f2469704de32c0ad419def0f4a84b7a138a79532238114/nh3-0.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80", size = 577126, upload-time = "2025-10-30T11:17:22.755Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3e/f5a5cc2885c24be13e9b937441bd16a012ac34a657fe05e58927e8af8b7a/nh3-0.3.2-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e", size = 1431980, upload-time = "2025-10-30T11:17:25.457Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f7/529a99324d7ef055de88b690858f4189379708abae92ace799365a797b7f/nh3-0.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8", size = 820805, upload-time = "2025-10-30T11:17:26.98Z" }, - { url = "https://files.pythonhosted.org/packages/3d/62/19b7c50ccd1fa7d0764822d2cea8f2a320f2fd77474c7a1805cb22cf69b0/nh3-0.3.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866", size = 803527, upload-time = "2025-10-30T11:17:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/f022273bab5440abff6302731a49410c5ef66b1a9502ba3fbb2df998d9ff/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131", size = 1051674, upload-time = "2025-10-30T11:17:29.909Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f7/5728e3b32a11daf5bd21cf71d91c463f74305938bc3eb9e0ac1ce141646e/nh3-0.3.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5", size = 1004737, upload-time = "2025-10-30T11:17:31.205Z" }, - { url = "https://files.pythonhosted.org/packages/53/7f/f17e0dba0a99cee29e6cee6d4d52340ef9cb1f8a06946d3a01eb7ec2fb01/nh3-0.3.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07", size = 911745, upload-time = "2025-10-30T11:17:32.945Z" }, - { url = "https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7", size = 797184, upload-time = "2025-10-30T11:17:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/08/a1/73d8250f888fb0ddf1b119b139c382f8903d8bb0c5bd1f64afc7e38dad1d/nh3-0.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87", size = 838556, upload-time = "2025-10-30T11:17:35.875Z" }, - { url = "https://files.pythonhosted.org/packages/d1/09/deb57f1fb656a7a5192497f4a287b0ade5a2ff6b5d5de4736d13ef6d2c1f/nh3-0.3.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a", size = 1006695, upload-time = "2025-10-30T11:17:37.071Z" }, - { url = "https://files.pythonhosted.org/packages/b6/61/8f4d41c4ccdac30e4b1a4fa7be4b0f9914d8314a5058472f84c8e101a418/nh3-0.3.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131", size = 1075471, upload-time = "2025-10-30T11:17:38.225Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c6/966aec0cb4705e69f6c3580422c239205d5d4d0e50fac380b21e87b6cf1b/nh3-0.3.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0", size = 1002439, upload-time = "2025-10-30T11:17:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c8/97a2d5f7a314cce2c5c49f30c6f161b7f3617960ade4bfc2fd1ee092cb20/nh3-0.3.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6", size = 987439, upload-time = "2025-10-30T11:17:40.81Z" }, - { url = "https://files.pythonhosted.org/packages/0d/95/2d6fc6461687d7a171f087995247dec33e8749a562bfadd85fb5dbf37a11/nh3-0.3.2-cp38-abi3-win32.whl", hash = "sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b", size = 589826, upload-time = "2025-10-30T11:17:42.239Z" }, - { url = "https://files.pythonhosted.org/packages/64/9a/1a1c154f10a575d20dd634e5697805e589bbdb7673a0ad00e8da90044ba7/nh3-0.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe", size = 596406, upload-time = "2025-10-30T11:17:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7e/a96255f63b7aef032cbee8fc4d6e37def72e3aaedc1f72759235e8f13cb1/nh3-0.3.2-cp38-abi3-win_arm64.whl", hash = "sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104", size = 584162, upload-time = "2025-10-30T11:17:44.96Z" }, +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/5f/1d19bdc7d27238e37f3672cdc02cb77c56a4a86d140cd4f4f23c90df6e16/nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", size = 20743, upload-time = "2026-04-25T10:44:16.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/b0/8587ac42a9627ab88e7e221601f1dfccbf4db80b2a29222ea63266dc9abc/nh3-0.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", size = 1420126, upload-time = "2026-04-25T10:43:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/1dbc4d0c43f12e8c1784ede17eaee6f061d4fbe5505757c65c49b2ceab95/nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", size = 793943, upload-time = "2026-04-25T10:43:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/47/9f/d6758d7a14ee964bf439cc35ae4fa24a763a93399c8ef6f22bd11d532d29/nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", size = 841150, upload-time = "2026-04-25T10:43:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/d5d1ae8374612c98f390e1ea7c610fa6c9716259a03bbf4d15b269f40073/nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", size = 1008415, upload-time = "2026-04-25T10:43:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/d13a9c3fd2d9c131a2a281737380e9379eb0f8c33fea24c2b923aaafbb15/nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", size = 1092706, upload-time = "2026-04-25T10:43:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/bb/57/2f3add7f8680fcc896afa6a675cb2bab09982853ee8af40bad621f6b61c4/nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", size = 1048346, upload-time = "2026-04-25T10:43:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/2f9e4ffa82863074d1361bfe949bc46393d91b3411579dfbbd090b24cac5/nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", size = 1029038, upload-time = "2026-04-25T10:43:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/e8/10/2804deb3f3315184c9cae41702e293c87524b5a21f766b07d7fe3ffbcfbb/nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", size = 603263, upload-time = "2026-04-25T10:43:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/f6685248b49f7548fc9a8c335ab3a52f68610b72e8a61576447151e4e2e6/nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", size = 616866, upload-time = "2026-04-25T10:43:51.005Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/d8c9018635d4acfefde6b68470daa510eed715a350cbaa2f928ba0609f81/nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", size = 602566, upload-time = "2026-04-25T10:43:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", size = 1442393, upload-time = "2026-04-25T10:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/072120d506978ab053e1732d0efa7c86cb478fee0ee098fda0ac0d31cb34/nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", size = 837722, upload-time = "2026-04-25T10:43:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/d4e06e28c5ad1c4b065f89737d02631bd49f1660b6ebcf17a87ffcd201da/nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", size = 822872, upload-time = "2026-04-25T10:43:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/0a/62/50659255213f241ec5797ae7427464c969397373e83b3659372b341ae869/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", size = 1100031, upload-time = "2026-04-25T10:43:58.098Z" }, + { url = "https://files.pythonhosted.org/packages/00/7a/a12ae77593b2fcf3be25df7bc1c01967d0de448bdb4b6c7ec80fe4f5a74f/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4", size = 1057669, upload-time = "2026-04-25T10:43:59.328Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/5647dc04c0233192a3956fc91708822b21403a06508cacf78083c68e7bf0/nh3-0.3.5-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b", size = 914795, upload-time = "2026-04-25T10:44:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1", size = 806976, upload-time = "2026-04-25T10:44:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/85/01/26761e1dc2b848e65a62c19e5d39ad446283287cd4afddc89f364ab86bc9/nh3-0.3.5-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc", size = 834904, upload-time = "2026-04-25T10:44:03.454Z" }, + { url = "https://files.pythonhosted.org/packages/33/53/0766113e679540ac1edc1b82b1295aecd321eeb75d6fead70109a838b6ee/nh3-0.3.5-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b", size = 857159, upload-time = "2026-04-25T10:44:05.003Z" }, + { url = "https://files.pythonhosted.org/packages/58/36/734d353dfaf292fed574b8b3092f0ef79dc6404f3879f7faaa61a4701fad/nh3-0.3.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4", size = 1018600, upload-time = "2026-04-25T10:44:06.18Z" }, + { url = "https://files.pythonhosted.org/packages/6b/aa/d9c59c1b49669fcb7bababa55df82385f029ad5c2651f583c3a1141cfdd1/nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", size = 1103530, upload-time = "2026-04-25T10:44:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/cdd210bfb8d9d43fb02fc3c868336b9955934d8e15e66eb1d15a147b8af0/nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", size = 1061754, upload-time = "2026-04-25T10:44:09.362Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/7a39e72e668c8445bdd95e494b3e21cfdddc68329be8ea3522c8befb46c4/nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", size = 1040938, upload-time = "2026-04-25T10:44:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/4c/fc2f9ed208a3801a319f59b5fea03cdc20cf3bd8af14be930d3a8de01224/nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", size = 611445, upload-time = "2026-04-25T10:44:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/db/1a/e4c9b5e2ae13e6092c9ec16d8ca30646cb01fcdea245f36c5b08fd21fbd5/nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", size = 626502, upload-time = "2026-04-25T10:44:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, ] [[package]] @@ -2647,7 +3500,8 @@ name = "numpy" version = "2.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } wheels = [ @@ -2764,12 +3618,15 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.2" +version = "2.4.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2777,88 +3634,108 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, - { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, - { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, - { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, - { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, - { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, - { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, - { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, - { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, - { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, - { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/44/1383ee4d1e916a9e610e46c876b5c83ea023526117d23cd911983929ec34/numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288", size = 16969261, upload-time = "2026-05-15T20:22:23.036Z" }, + { url = "https://files.pythonhosted.org/packages/3d/61/54bacfbec7550bc398e6b6d9a861db35d64f75844e1d7920f5722c3cd5e7/numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7", size = 14964009, upload-time = "2026-05-15T20:22:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/7a/55/fe86c64561761f185339c26001164a2687bd4787af681e961431abd2d534/numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6", size = 5469106, upload-time = "2026-05-15T20:22:28.13Z" }, + { url = "https://files.pythonhosted.org/packages/2f/74/cf29b8317627f0e3aa2c9fb332d386bd734308cecd9e07da9f407d9ce0c3/numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8", size = 6798945, upload-time = "2026-05-15T20:22:30.061Z" }, + { url = "https://files.pythonhosted.org/packages/80/a9/b61730a17fa87d5abb13ce560a1b4ce3485d37a13e03eb7b414e598e72f8/numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772", size = 15967025, upload-time = "2026-05-15T20:22:32.328Z" }, + { url = "https://files.pythonhosted.org/packages/03/39/70bcd187eb4d223c21fde02c2bdfbffbffef3288cbb3947c04c74ae39a08/numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd", size = 16918685, upload-time = "2026-05-15T20:22:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/400fd1315bbe228af3937cf8a74e32023df6217af36077919d00adc382e4/numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059", size = 17322963, upload-time = "2026-05-15T20:22:37.557Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/bbbafb657e6f6ee826b4ecdb8722a2e0aae4a981888eaf59eae6a535cc13/numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55", size = 18651594, upload-time = "2026-05-15T20:22:40.449Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/857a515154a2a18b0dfae04089600d166d352d473ec17a0680d879582d06/numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf", size = 6233849, upload-time = "2026-05-15T20:22:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/d215f3fb93541617adb5d58b3b9508e8a6413e499711e0adc0b80bcb445d/numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864", size = 12608238, upload-time = "2026-05-15T20:22:45.229Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c4/611d66d3fcfa931954d37a19ce5575f3283d023e89ff0df6ad43b334ae9c/numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b", size = 10479452, upload-time = "2026-05-15T20:22:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/3275231e98620002681c922e792db04d72c356e9d8073c387344fc0e4ff1/numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef", size = 16689166, upload-time = "2026-05-15T20:22:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/000aab6a16bdec53307f0f72546b57a3ac9266a62d8c257bee97d85fd078/numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862", size = 14699514, upload-time = "2026-05-15T20:22:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/47/cc/ddaf3af9c46966fef5be879256f213d85a0c56c75d07a3b7defec7cf6b4c/numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507", size = 5204601, upload-time = "2026-05-15T20:22:56.257Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/627fadd11959b3c7759008f34c92a35af8ff942dd8284a66ced648bbe516/numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa", size = 6551360, upload-time = "2026-05-15T20:22:58.334Z" }, + { url = "https://files.pythonhosted.org/packages/a1/47/0728b986b8682d742ff68c16baa5af9d185484abfc635c5cc700f44e62be/numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72", size = 15671157, upload-time = "2026-05-15T20:23:01.081Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0b/b905ae82d9419dc38123523862db64978ca2954b69609c3ae8fdaca1084c/numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912", size = 16645703, upload-time = "2026-05-15T20:23:04.358Z" }, + { url = "https://files.pythonhosted.org/packages/5f/24/e27fc3f5236b4118ed9eed67111675f5c61a07ea333acec87c869c3b359d/numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef", size = 17021018, upload-time = "2026-05-15T20:23:07.021Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a7/9041af38d527ab80a06a93570a77e29425b41507ad41f6acf5da78cfb4a4/numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7", size = 18368768, upload-time = "2026-05-15T20:23:09.44Z" }, + { url = "https://files.pythonhosted.org/packages/49/82/326a014442f32c2663434fd424d9298791f47f8a0f17585ad60519a5606e/numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5", size = 5962819, upload-time = "2026-05-15T20:23:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/cbf5d391b0b3a5e8cad264603e2fae256b0bde8ce43566b13b78faedc659/numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6", size = 12321621, upload-time = "2026-05-15T20:23:14.305Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d0/0f18909d9bc37a5f3f969fc737d2bb5df9f2ff295f71b467e6f52a0d6c4e/numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453", size = 10221430, upload-time = "2026-05-15T20:23:16.887Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a4/fb50657c7cab297bf34edcd60a074cb0647f61771430d6363575274160fe/numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277", size = 16684760, upload-time = "2026-05-15T20:23:19.436Z" }, + { url = "https://files.pythonhosted.org/packages/3e/43/87e731299b9408eda705b3b9cb31c7bceb9347d2af9cbb16b2b1e4b5bc0f/numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0", size = 14694117, upload-time = "2026-05-15T20:23:21.832Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/0b2bb8acea222e9dd6e582afc2bc553b89b8833cbdccc68e68f050fb31f8/numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419", size = 5199141, upload-time = "2026-05-15T20:23:24.066Z" }, + { url = "https://files.pythonhosted.org/packages/39/60/b6972b5d47033d90000f0097c81a98b9486589a2d7003bf725bff275cb0d/numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685", size = 6546954, upload-time = "2026-05-15T20:23:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e9/ed667cb12c11ca0adde431f685d3a5dd78e6f78b27228c581c8415198e9e/numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c", size = 15669430, upload-time = "2026-05-15T20:23:28.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/e5/679f6ffeb01294b0008e5ada4a113cb47617bc0e1819a529fd7973c6d7f4/numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83", size = 16633390, upload-time = "2026-05-15T20:23:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/36/46/42bfffc9a780ec902ccd7470d3219192ee82b7b442710307dd85b4d121b0/numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566", size = 17020709, upload-time = "2026-05-15T20:23:34.08Z" }, + { url = "https://files.pythonhosted.org/packages/44/00/3e840bfee0cc6cec22209f2c97057f26eeb30de031e4933b4dfc0395416c/numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097", size = 18357818, upload-time = "2026-05-15T20:23:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/cb/3447b400b9da84134575486f0f656541559b00d4b262477bce9b678bbca8/numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33", size = 5961114, upload-time = "2026-05-15T20:23:39.586Z" }, + { url = "https://files.pythonhosted.org/packages/28/f9/a90d2220ffcdc0798f5d55bb5d5463cd6254ec9ef43f384dae80217d7a2f/numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42", size = 12318553, upload-time = "2026-05-15T20:23:41.436Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/96f531fb3234545315152d34efdf3de7daee81254448447eb619e8d16967/numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97", size = 10222200, upload-time = "2026-05-15T20:23:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f4/a291caab5a3c520babf93ff77c54fd5fdb1ebbc3296cee2eb2146ce773b1/numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb", size = 14821438, upload-time = "2026-05-15T20:23:45.911Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/13dbb1159b864370568e7309063fd72667984df89db74e9caeb175d067c7/numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4", size = 5326663, upload-time = "2026-05-15T20:23:48.18Z" }, + { url = "https://files.pythonhosted.org/packages/7c/99/d233408072a0e019e2288e27edd23f7d572ccd4a73d1539baa3270ede85d/numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed", size = 6646874, upload-time = "2026-05-15T20:23:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/c5/00/eeb6f193dfe767725e952e0464f3e51f44145c5dd261cd7389aa36ac0713/numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6", size = 15728147, upload-time = "2026-05-15T20:23:51.655Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c9/b8ed039f1fde1b13a8807c893e7e2f9432a379f4d6401edecf0028da5b2c/numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976", size = 16681770, upload-time = "2026-05-15T20:23:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/11/5b/0198ef6cb7016eca6d895d392106012138127fab23f46637e76d5e25c9f5/numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222", size = 17086218, upload-time = "2026-05-15T20:23:56.646Z" }, + { url = "https://files.pythonhosted.org/packages/f0/fe/8821f3cfc660ae84c92ee158505941874b62c56a42e035a41425228cd8cf/numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656", size = 18403542, upload-time = "2026-05-15T20:23:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/0e/00/e64ecaf498865e7b091f57658b2c522503e5d1b70e43b807f5f8247e1d88/numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb", size = 6084903, upload-time = "2026-05-15T20:24:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/20/c0/354997dedaf74e8311c2cf9a6027b476fd8d424cb92189cc0ae2b25f501c/numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc", size = 12458420, upload-time = "2026-05-15T20:24:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/66/dc/917ee5ea4a31ca1a6e4c9a85386477efa318dcc60db257c5ef4adda096c1/numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db", size = 10291826, upload-time = "2026-05-15T20:24:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c1/3be0bf102fc17cff5bd142e3be0bfffabec6fa46da0a462396c76b0765d0/numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61", size = 16683455, upload-time = "2026-05-15T20:24:08.988Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3e/0742d724901fa36bc54b338c6e62e463a7601180da896aa44978f0adf004/numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77", size = 14704577, upload-time = "2026-05-15T20:24:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/25/1c/196c610ff4c6782d697ba780ebdc1616be143213701bf22c1a270f3bf7dd/numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361", size = 5209756, upload-time = "2026-05-15T20:24:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/52/c0/23fb1bc506f774e03db66219a2830e720f4d3dbcaaddf855a7ff7bb6d96f/numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29", size = 6543937, upload-time = "2026-05-15T20:24:16.223Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/db4662c26e68520afcc84d672a6f9f5294063dee0e57a46d61afdaa7f9ed/numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce", size = 15685292, upload-time = "2026-05-15T20:24:17.978Z" }, + { url = "https://files.pythonhosted.org/packages/43/80/1315439acedd8398319bac177d6de3d48ab39c62cc0c810f74f0a9a73996/numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a", size = 16638528, upload-time = "2026-05-15T20:24:20.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/81/364388600932618fe735d97fdd2437cb8dd87a23377ac11d8b9d5db098b7/numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23", size = 17036709, upload-time = "2026-05-15T20:24:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/32/4a/a1185b18a94a6d9587e54b437e7d0ba36ecf6e614f1bea03f5249912c64e/numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25", size = 18363254, upload-time = "2026-05-15T20:24:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8e/95c1d2ed15ae97750ede8c8a0ac487c9c01207afff430f47078b1d9d7dc5/numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080", size = 6010184, upload-time = "2026-05-15T20:24:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/aa/92/d063df4d63d988b20d881856c74df76c0c1786229bb870f3a52af0981d4d/numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f", size = 12450344, upload-time = "2026-05-15T20:24:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/3d/64/c0ae481f7c3b2f85869bcd8fc5d30aa7c96b394162eef9c9315957f115c5/numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957", size = 10495674, upload-time = "2026-05-15T20:24:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/57/89/c5a4c677acf17aa50ba09a15e61812f90baac42bb6ca38d112e005858351/numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3", size = 14824078, upload-time = "2026-05-15T20:24:34.669Z" }, + { url = "https://files.pythonhosted.org/packages/e7/52/57e7144284f6b51ba93523e495ff239260b1ecd5257e3700a436332e5688/numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be", size = 5329246, upload-time = "2026-05-15T20:24:36.957Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/09dbce80fd4a7db4318f2fc01eec0ae76f29306442b5a32d4b811d082cdf/numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7", size = 6649877, upload-time = "2026-05-15T20:24:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/dbdb23e82d540b757690ef13f011c386fca6a63848eec6136baf8ce7cbed/numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40", size = 15730534, upload-time = "2026-05-15T20:24:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bd/68f6e9b3c20decf40ac06708a7b506757e3a8588efed32988d1b747316be/numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3", size = 16679741, upload-time = "2026-05-15T20:24:44.874Z" }, + { url = "https://files.pythonhosted.org/packages/39/1d/0fcac0b6b4ea1b50ca8fca05a34bed5c8d56e34c1cb5ffb04cf76109ac3c/numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c", size = 17085598, upload-time = "2026-05-15T20:24:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e8/a472b2564cf6cc498ad7aa9741d9832648221b8ab8cc0dbef41faa248ede/numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963", size = 18403855, upload-time = "2026-05-15T20:24:50.474Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a4/da82196f8cc4bd28ecf17bd57008c84f3d4696caf06753d9bad45e4ad749/numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5", size = 6156900, upload-time = "2026-05-15T20:24:53.134Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/860959b91a73d9a085006554fa3850da51a7ffab64599bac5097243438ab/numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a", size = 12638906, upload-time = "2026-05-15T20:24:55.009Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2a/bbd3097913083ad07c0f28fc9629666221fc18923e17ce97ae22a5dccdd6/numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b", size = 10565875, upload-time = "2026-05-15T20:24:57.425Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5d/9a644cfb841bc76b584afc3af1708b3bf6c5cb51fc84a7008246cd93b7b7/numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95", size = 16847544, upload-time = "2026-05-15T20:24:59.746Z" }, + { url = "https://files.pythonhosted.org/packages/56/8f/4fe5e3ba76d858dae1fe79078818c0520447335be0082c0dedf82719cc08/numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70", size = 14889039, upload-time = "2026-05-15T20:25:03.179Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6f/79f195abf922ecc43e7d0eb6cc969462a71b524a35bcd1fa26b4a1d7406a/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f", size = 5394106, upload-time = "2026-05-15T20:25:05.659Z" }, + { url = "https://files.pythonhosted.org/packages/58/6f/79cd6247205802bcbd10b40ea087e20ded526e10e9be224d34de832b216e/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83", size = 6708718, upload-time = "2026-05-15T20:25:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/d7/22/5f378a9d4633c98f28c4709d4144b1a4630c5c09e109d2e781e2d26c8fe1/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38", size = 15798292, upload-time = "2026-05-15T20:25:10.689Z" }, + { url = "https://files.pythonhosted.org/packages/63/1c/cec582febef798c99888892d92dc1d28dfe29cb427c41f44d13d0dec208f/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198", size = 16747406, upload-time = "2026-05-15T20:25:13.879Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dc/d358a16a6fec86cf736b8fbe67386044b3fa2aded1a80cff90e836799301/numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20", size = 12504085, upload-time = "2026-05-15T20:25:16.667Z" }, +] + +[[package]] +name = "optuna" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic", version = "1.16.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "alembic", version = "1.18.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "colorlog" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/9b/62f120fb2ecbc4338bee70c5a3671c8e561714f3aa1a046b897ff142050e/optuna-4.8.0.tar.gz", hash = "sha256:6f7043e9f8ecb5e607af86a7eb00fb5ec2be26c3b08c201209a73d36aff37a38", size = 482603, upload-time = "2026-03-16T04:59:58.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/24/7c731839566d30dc70556d9824ef17692d896c15e3df627bce8c16f753e1/optuna-4.8.0-py3-none-any.whl", hash = "sha256:c57a7682679c36bfc9bca0da430698179e513874074b71bebedb0334964ab930", size = 419456, upload-time = "2026-03-16T04:59:56.977Z" }, ] [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +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/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { 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]] @@ -2867,7 +3744,8 @@ version = "2.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -2936,12 +3814,15 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.0" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -2950,68 +3831,68 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, - { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, - { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, - { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, - { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, - { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, - { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, - { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, - { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, - { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, - { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, - { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, - { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, - { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, - { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, - { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, - { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, - { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, - { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, - { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, - { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, - { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, - { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, - { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, - { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, - { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, - { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, - { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, - { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, + { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +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/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { 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]] @@ -3021,7 +3902,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } wheels = [ @@ -3033,7 +3914,8 @@ name = "pillow" version = "11.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } wheels = [ @@ -3146,12 +4028,15 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.0" +version = "12.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -3160,98 +4045,98 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, - { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, - { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, - { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, - { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, - { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, - { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, - { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, - { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, - { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, - { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, - { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, - { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, - { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, - { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, - { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, - { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, - { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, + { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, + { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, + { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] [[package]] @@ -3259,7 +4144,8 @@ name = "platformdirs" version = "4.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } wheels = [ @@ -3268,12 +4154,15 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -3282,22 +4171,22 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] name = "plotly" -version = "6.5.2" +version = "6.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "narwhals" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/4f/8a10a9b9f5192cb6fdef62f1d77fa7d834190b2c50c0cd256bd62879212b/plotly-6.5.2.tar.gz", hash = "sha256:7478555be0198562d1435dee4c308268187553cc15516a2f4dd034453699e393", size = 7015695, upload-time = "2026-01-14T21:26:51.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/7f/0f100df1172aadf88a929a9dbb902656b0880ba4b960fe5224867159d8f4/plotly-6.7.0.tar.gz", hash = "sha256:45eea0ff27e2a23ccd62776f77eb43aa1ca03df4192b76036e380bb479b892c6", size = 6911286, upload-time = "2026-04-09T20:36:45.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/67/f95b5460f127840310d2187f916cf0023b5875c0717fdf893f71e1325e87/plotly-6.5.2-py3-none-any.whl", hash = "sha256:91757653bd9c550eeea2fa2404dba6b85d1e366d54804c340b2c874e5a7eb4a4", size = 9895973, upload-time = "2026-01-14T21:26:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/90/ad/cba91b3bcf04073e4d1655a5c1710ef3f457f56f7d1b79dcc3d72f4dd912/plotly-6.7.0-py3-none-any.whl", hash = "sha256:ac8aca1c25c663a59b5b9140a549264a5badde2e057d79b8c772ae2920e32ff0", size = 9898444, upload-time = "2026-04-09T20:36:39.812Z" }, ] [[package]] @@ -3332,7 +4221,8 @@ name = "pycparser" version = "2.23" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ @@ -3344,7 +4234,16 @@ name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", @@ -3365,11 +4264,128 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +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/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { 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 = "pymoo" +version = "0.6.1.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "alive-progress", marker = "python_full_version < '3.10'" }, + { name = "autograd", marker = "python_full_version < '3.10'" }, + { name = "cma", marker = "python_full_version < '3.10'" }, + { name = "deprecated", marker = "python_full_version < '3.10'" }, + { name = "dill", marker = "python_full_version < '3.10'" }, + { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/ed/ec5a76bb1556b774a67806c08234dab0e603509846b6b94934da59e5f4bd/pymoo-0.6.1.5.tar.gz", hash = "sha256:9ce71eaceb2f5cccf8c5af53102cf6d96fa911452addaf48fb971a60621f8364", size = 258027, upload-time = "2025-05-26T21:59:31.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/69/4a8c724ff65654cea9b7c6532ed271dcef60171f62cae01a407259e7b16c/pymoo-0.6.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:00b5aec75e1ebdb13f537a45b17a33a8fd7ea7437b9bcfdc1a72e56ddf26dd7a", size = 1560163, upload-time = "2025-05-26T21:58:21.197Z" }, + { url = "https://files.pythonhosted.org/packages/56/be/ef5c9d25c838a37fc5e612dcb1777580e646522e6896d8dd7354d858c867/pymoo-0.6.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdfe6b82582831a57bc441bb078b2dfa10654b04dc47208e714e2312c123cb4", size = 945392, upload-time = "2025-05-26T21:58:23.709Z" }, + { url = "https://files.pythonhosted.org/packages/fa/47/d2f52c59f1972279b030362f5a88d2de51c3936025e0d639eb37b6618bcf/pymoo-0.6.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48c0f930f344c2fd2fd82d7b30dd7e7d2613437c9519c2aaee73cfca7707fc82", size = 4213223, upload-time = "2025-05-26T21:58:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/da5881ecd278f320e639eb12195c0aa5356b00c7b3066e3fc6a9b0fa4d65/pymoo-0.6.1.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fc9079a7d4d4e392028deb8821a13b77a4ff834727725dbf62cddf44fd0a6e98", size = 5638677, upload-time = "2025-05-26T21:58:29.919Z" }, + { url = "https://files.pythonhosted.org/packages/3b/72/be83c99f185a574f14c001ac57c2886be0f675ed330865035674a0f2575a/pymoo-0.6.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:874c7f7c6da71520c230aa0f7150c949854d68eef6f57de4d9b8bbd4bc9dfb69", size = 2006657, upload-time = "2025-05-26T21:58:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/06/bd/473e1e813ffba82c2c77f5e90553bc56a8ff8f02ffac1a5c67debcf7126c/pymoo-0.6.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2340e9da2e09c423d47cfe553375134702598e43701dc1cbd14c71010b381666", size = 1557733, upload-time = "2025-05-26T21:58:34.782Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/7a519e8029d97647457a9e8c14807c04e7b8b31cc845dcd1de238c8d5761/pymoo-0.6.1.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c0d0a1c349ae6973ea6e0cedf208c760d557be6aca0fb86aff6db155e263dfef", size = 943827, upload-time = "2025-05-26T21:58:36.904Z" }, + { url = "https://files.pythonhosted.org/packages/db/0e/51cd797554fda6d9fb930d6f8d98ef9f01344b7a682e0ab89eefce616fa6/pymoo-0.6.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fd159285c5637d5c68d758c3498f13f066635455e2b0f2b3a43b9d44704c75", size = 4409417, upload-time = "2025-05-26T21:58:39.904Z" }, + { url = "https://files.pythonhosted.org/packages/5e/63/c921a65be1afa3c5eae0de78e17a3dc592c21de64de50e6bfb7645543154/pymoo-0.6.1.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:04a40005d4c18e194a380e360162063577cc1c206d5ca40f0dd463168e0efaf1", size = 5831827, upload-time = "2025-05-26T21:58:44.807Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/16a954eed286790fba89738d64cac4840434e6d05883ad559a254d9e9659/pymoo-0.6.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2fde9e9b6ed21b743e466d7a2225cf4aa8fc81408fe104948e70fbb0f5fd53de", size = 2006610, upload-time = "2025-05-26T21:58:47.384Z" }, + { url = "https://files.pythonhosted.org/packages/42/23/45dddc4897e384534d059688576b02016af72270d7ff18b14816a30a4c30/pymoo-0.6.1.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31d9f9522337c6ddfa6fc7670daa9ce4c777c104283824c3e6a2c482d8cde5b8", size = 1564924, upload-time = "2025-05-26T21:58:49.596Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4e/5ffa473b30b7ab44b3bb3c4bd4b77d81c8975fb8bb17381275e106137838/pymoo-0.6.1.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57dd99fd7fff871d42289646ee7899f5f85535a74d4fefcca900a9dde1067c07", size = 944016, upload-time = "2025-05-26T21:58:51.399Z" }, + { url = "https://files.pythonhosted.org/packages/cf/9b/27e7e1e858c1f01b78a3de7e137c68905c693f1b81cee9826ff8b6bf6b78/pymoo-0.6.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:247099da5cf52092529089cd2b69d6cb959db9081d88789d6d1155778f392041", size = 4382163, upload-time = "2025-05-26T21:58:54.76Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/2dd71f8b75cad7251843a9b04587417171ee64233e8d1ef86fa59d385c7d/pymoo-0.6.1.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db6e562fad10afcfb250de116f958d7606f9ccb95d9a6e84b1c26378384cd736", size = 5786773, upload-time = "2025-05-26T21:58:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6a/f6733edb12bf452ca21d90bda2b50e13ade23c76e3018e092234b361840c/pymoo-0.6.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:44a151f83b9e455cdf1a8d63383c378b871c44592b6314167a39be3694a2fb01", size = 2009815, upload-time = "2025-05-26T21:59:00.778Z" }, + { url = "https://files.pythonhosted.org/packages/2e/5e/260d77d5d44ee276fca63c902a38dbfa5315b13db4a856f4b4ede5769754/pymoo-0.6.1.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9ddaeb66ce18d473cdfdfd70c7e63e1cd7cddf47879e79bca1f8eab379a74413", size = 1552512, upload-time = "2025-05-26T21:59:02.748Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3f/38538bb89e92eb10357bc2aa8dcefdfa25ec01b57a2b4cd419e704de3139/pymoo-0.6.1.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:875d06c0f0617ea73eaedb810cc25d55b40b7ddf77db23f59bca51a18eab5079", size = 937888, upload-time = "2025-05-26T21:59:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/4d/75/7a7e1ddea474ef3e0d1845c20e7792743a8b01850e2956a6f776dbf87f46/pymoo-0.6.1.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da0d2afe9fa6a94fbec3fe970fa9426309b668eefb1eb796f44bfa186cf2c5ad", size = 4331420, upload-time = "2025-05-26T21:59:08.601Z" }, + { url = "https://files.pythonhosted.org/packages/85/6a/85e26ad9b046e89a4a77bee7f0ed3b3d77ecb2e743727d09187473346719/pymoo-0.6.1.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0926f8ba84fc1e104b30ccdcf0dd5ed886be209f7de6d729fe115cdd3fdec084", size = 5775567, upload-time = "2025-05-26T21:59:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1f/479758d597229563bf9d2003911bdb0829f031da4d1577bf76ff61e5b704/pymoo-0.6.1.5-cp313-cp313-win_amd64.whl", hash = "sha256:36543ab8690c9afb4a07c795f58018223394b86c5ba0ce6044f7f28c193dfacc", size = 2008812, upload-time = "2025-05-26T21:59:15.135Z" }, + { url = "https://files.pythonhosted.org/packages/69/ed/91fc387f71c5ec59a793156339650d6fd9e26688849710d85044160662f0/pymoo-0.6.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dd9e9a898536402e40ca014d01ba78ba78cf464f4a5efa4eca15c30856f815f5", size = 1568278, upload-time = "2025-05-26T21:59:17.115Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/e7460a80363a7efeaab1d51879625d5094a670cfc8db5a0132b54461fef5/pymoo-0.6.1.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:92379d2b0e0730822fdf95dcf331a097476bb9188b0fe7082b25e71c403ab2a0", size = 949569, upload-time = "2025-05-26T21:59:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/7d/13/d013504279621e801ef66273bade95504d9045fbfd534c825c93a67e43cb/pymoo-0.6.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1274cabeb247ff1238479a60080ebc84a7003da0b6c0b44ab8dc2191717d6a79", size = 4219603, upload-time = "2025-05-26T21:59:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/85/dc/9438b12ffe64fc16d63e168fa1d62db0ba05af110fcb20000d52181d9542/pymoo-0.6.1.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d8c9fdaf9eca6c5abb961dd6bb763a7e751e4a142aea3d234414f0cc954e70b", size = 5652607, upload-time = "2025-05-26T21:59:26.513Z" }, + { url = "https://files.pythonhosted.org/packages/86/87/2dcb766f00c4c910d7c9375c9098bae0dc06a7704be04d4d30c107740a04/pymoo-0.6.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:54e0e1a448bc967db73dfa46a6c7a4daf1f9c5570e1cf0e8ca4b713ca6f14ea8", size = 2010532, upload-time = "2025-05-26T21:59:29.459Z" }, +] + +[[package]] +name = "pymoo" +version = "0.6.1.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "alive-progress", marker = "python_full_version >= '3.10'" }, + { name = "autograd", marker = "python_full_version >= '3.10'" }, + { name = "cma", marker = "python_full_version >= '3.10'" }, + { name = "deprecated", marker = "python_full_version >= '3.10'" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "moocore", marker = "python_full_version >= '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/6c/5637688bb0e484ad7cd84e9f24f575d1b3c4ef28ce0974836bce7660106a/pymoo-0.6.1.6.tar.gz", hash = "sha256:d48077c7b612b149e7db5351459bf96a0950e84ebcd5b7b953bf46b3dcf1ac28", size = 1216128, upload-time = "2025-11-25T03:18:30.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/ec/091de8458b96421030aadfa10b0e4c25bd75d1c7123d1b24d76d8f7d902c/pymoo-0.6.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2d48fa7f8f8ea314405ad49a6b7f3dcf8f12c2024e319e2d47ede0937f8b39b", size = 2408116, upload-time = "2025-11-25T03:16:40.894Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ce/cbbb604855fd9d8acce7f677f0c8d56b26722bae8b6ed1f71cf0226d1a85/pymoo-0.6.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c8cfd085b74b3f673378109d8d0734d0cab460e3e78c09f691120ac0c2698aad", size = 1874607, upload-time = "2025-11-25T03:16:43.279Z" }, + { url = "https://files.pythonhosted.org/packages/52/39/ddbb1aab87dd8d43fb0ef996697f71bdfe9c1b1d80431dffc843137cf7f4/pymoo-0.6.1.6-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a9cde6ff7cc8497458166e2c14734bf4879c76256412cce0de2af86d2b71a8", size = 4963620, upload-time = "2025-11-25T03:16:47.068Z" }, + { url = "https://files.pythonhosted.org/packages/de/31/1db5fb5184dd422c62084e68e72de2dccfeea37ef5764ec6c1c152637ef0/pymoo-0.6.1.6-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:007bdabe638d5cc1ff98d1df096e7cea42f266ecc7b84fc0aac695a20b6f02f6", size = 5033031, upload-time = "2025-11-25T03:16:50.849Z" }, + { url = "https://files.pythonhosted.org/packages/3e/39/a53c970509bef5cec678adca422029e3be3a3adbd9563d521498b5a443e1/pymoo-0.6.1.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62599637724f74820e16d813e6ca86af1733add4251e22b5a6c677df75dec06b", size = 5904102, upload-time = "2025-11-25T03:16:55.102Z" }, + { url = "https://files.pythonhosted.org/packages/11/3e/b76929def757beedf75deba214958af0a71548bab210b2cd1219265e4462/pymoo-0.6.1.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4f590fc62021d2583afdc88a2eb5ec878699786585f9ca82edf1fc32557ac96", size = 6088135, upload-time = "2025-11-25T03:16:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/d6/49/ce483ad86ad9d767e33488f5b1cc39544b21121b481ce4f3c15eb1b1c3e8/pymoo-0.6.1.6-cp310-cp310-win_amd64.whl", hash = "sha256:3b9dcb6959cf2b12c0f1d4ac971fa53074430eb976367b2e7eec0d0301423f31", size = 1830499, upload-time = "2025-11-25T03:17:01.616Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/0abcc60bd65b2cac34e951d4bbe87514f142772b894de30e643efcd8f02f/pymoo-0.6.1.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0a42b6f3c26b2725b1edb1d4cfb98e5640bb0638813080ba1aa0daf23b2fd05", size = 2402920, upload-time = "2025-11-25T03:17:04.179Z" }, + { url = "https://files.pythonhosted.org/packages/d5/00/c252683f897e8a8bac00d8b16210327a631372fd2f8f7d1344f02c96947e/pymoo-0.6.1.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3e990dea0ad84fa2f89462215e34960579dbb9fef4d880aecb09f50cc18e713", size = 1871767, upload-time = "2025-11-25T03:17:06.368Z" }, + { url = "https://files.pythonhosted.org/packages/93/6d/f9772585189eec4990e7a39a6c87bb4202cbb89b92fe1800c58897a1b467/pymoo-0.6.1.6-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642308eae98c5b8aee96272e10c6409dd97289ef214ec7383b39425f01549b5f", size = 5120039, upload-time = "2025-11-25T03:17:10.424Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a3/b48789ba434f0015dc9dd14ab918e0fb0bd8bfb354d8c0b8c491d62255bc/pymoo-0.6.1.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50472dfb93b90b4801c63070a4afc786c52f7f56b345f345024b39cd35ff06c4", size = 5190633, upload-time = "2025-11-25T03:17:14.331Z" }, + { url = "https://files.pythonhosted.org/packages/df/37/59b1b0e1a1331692b63e0e019cc2b32ed22a33621e447b5aeccbf7b64151/pymoo-0.6.1.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:38fa5bf507cd7c07eae0286948db79f761b5b4c5d5956a31fd0e5a771c83fa47", size = 6061562, upload-time = "2025-11-25T03:17:18.037Z" }, + { url = "https://files.pythonhosted.org/packages/71/06/5f5ceb1daa38693ed5fd83c0f1568e69c7a9b7b65973ea81b6a248d31817/pymoo-0.6.1.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a68fb9d807c1cb6f36e13c24731c496b1e98b9c8810125072f19bd56baa6a4d5", size = 6247686, upload-time = "2025-11-25T03:17:22.399Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9e/250d5ca97f9474acafb0162695716bf94ba68fdbbb953f269a513ad8c30a/pymoo-0.6.1.6-cp311-cp311-win_amd64.whl", hash = "sha256:df9520fd7fab0761d5698dd36eabcfb18b5eb970ddf01e10eb30b764e4bb72af", size = 1829941, upload-time = "2025-11-25T03:17:24.898Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/9c6b9b5f18349dd797c0e7effe0dfd51402fa15e7cb293a525d6f73ffe03/pymoo-0.6.1.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fd4715b4015b10ad3cf02e5eb9abb57fe4bfa4d3378f9076b76d60c1ddfa46d6", size = 2417332, upload-time = "2025-11-25T03:17:27.374Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/cc8a39a4ea8e425dbc94db44f35f9e5dda3e1c0fbfdfba9fe9c58db600ab/pymoo-0.6.1.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f62fb1e2ba15f22ff5569c6d364dbf9dfe8b3722a114e6bc1df3cc10f8dd5044", size = 1876468, upload-time = "2025-11-25T03:17:29.407Z" }, + { url = "https://files.pythonhosted.org/packages/15/e8/c025f7330039cb320280b770a28f843174bddf9372bb9727add1031d21b7/pymoo-0.6.1.6-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd2b0fb19ce3e71a177ed3b965ce2d4334ffb6aad8f621c124de6dafa09b7758", size = 5086314, upload-time = "2025-11-25T03:17:32.513Z" }, + { url = "https://files.pythonhosted.org/packages/65/bb/402205166c567bb0b5452e2176dbf2af4b88d3577096f53fdc95be8ef562/pymoo-0.6.1.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533de86c284bf3b7b6da871e2ae07a914eaa5d3a51f5c4e6d7c055d3471c4467", size = 5181206, upload-time = "2025-11-25T03:17:35.211Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/ef60d8e7674906347dc9877afb4343fd7b21d0bdea60f736db3b857ea903/pymoo-0.6.1.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a2e9524b07baef135a7143700cc65a504c2123c717a6924fa939947b4fdba73", size = 6006960, upload-time = "2025-11-25T03:17:38.252Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ec/79ca5ca57d4d9e40691d3d0153675e4b1520278637c372380addc66a5823/pymoo-0.6.1.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:819a27ff19c146a64853a86ebab02bc6bbf28fb9fcf119e93cfc855ec6314f72", size = 6208775, upload-time = "2025-11-25T03:17:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/08/b0/ac4f541e03fdd7e752c1e8ee766b1a6edcdff4d65930b3d593171eec02b3/pymoo-0.6.1.6-cp312-cp312-win_amd64.whl", hash = "sha256:5607e0359c96691158192bd3e2b939beab6d4ed25032ef4cb79edf588c2bb475", size = 1834431, upload-time = "2025-11-25T03:17:44.346Z" }, + { url = "https://files.pythonhosted.org/packages/7f/96/4e87b2330aaea8a23fe8fcfa3e1d7c60a57c4d968bdd330f2b9cbfeee7ea/pymoo-0.6.1.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:798e5af41527324303e56ac806f9ec34c65f31c2df9e904daeca68027bf91266", size = 2408764, upload-time = "2025-11-25T03:17:46.644Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/7b2ddc6f764b49a96a9653cc0f758cc773268d88b149ab889fdf42a5abfc/pymoo-0.6.1.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:01e848762e04d183e8dcc12e1cfb1e2cdd2c5efeda47961dbcf6c86c09751e30", size = 1872399, upload-time = "2025-11-25T03:17:48.817Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/a70078ae29fe3363e06b0b3c761d9a53474d1fa25ce1f4a3a890ec3cc99a/pymoo-0.6.1.6-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:718b9e971e436c2ded520c4a6fe8c83b7f305da1fcc539d6386d025c6e9bfe66", size = 5066526, upload-time = "2025-11-25T03:17:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/89/94/7896e833bd29d45e5e721b0f71fdef9cf895e593ac28e7f83585218709ed/pymoo-0.6.1.6-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:981bd5b8581e959814a0e51982be1a964945fb7f599cfe7857a63cbc1ad97d58", size = 5176441, upload-time = "2025-11-25T03:17:55.925Z" }, + { url = "https://files.pythonhosted.org/packages/f5/fe/6b8e2d82f118ef29fada61c0ac473b95cd0d5c742b0bad22bae178cde5c5/pymoo-0.6.1.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7c042120df86240c1451e2a1afb0f76b64acb8d736fb7687bc5e4aec40f7ff9e", size = 5984679, upload-time = "2025-11-25T03:17:59.917Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/5f1dd554fef9476642334dd923bc6943136f9d1af7dc97c304f0e3c1684e/pymoo-0.6.1.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5086f46020aa1a13a0e3142ef87b6487e91ea5d25b7b69467039f0d3f5a57cb2", size = 6201995, upload-time = "2025-11-25T03:18:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/287e3cae3b2bc3b387cbd1c8f0fecc5038190eef62d823f8b810dca43a95/pymoo-0.6.1.6-cp313-cp313-win_amd64.whl", hash = "sha256:c16e05b36e081d0ea6aee499346f65466811f63f5837f0d09de866624cb7355b", size = 1833324, upload-time = "2025-11-25T03:18:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/51/ec/f0f65e8abd724e62c9b5ebfcaa2280ed327edd33303d4acc8df1c832e07f/pymoo-0.6.1.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7d13e14434f0555fbeda20764d34be4dc94f53ae7040a337928d7bfe42d3422a", size = 2426044, upload-time = "2025-11-25T03:18:08.742Z" }, + { url = "https://files.pythonhosted.org/packages/dd/19/0113bd220b0e20834216a28fad49eac88c3770660d32aeca7ac29b1a382e/pymoo-0.6.1.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:466f88073c9f498bcf75334a2e47279968003d91b7fe01df1b056b5a6c3434d8", size = 1888556, upload-time = "2025-11-25T03:18:11.451Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/e2bba2460219be070316bd0f93a61ff72fc6ae3d14b964cf4e837acf0f5f/pymoo-0.6.1.6-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:791188677b42e104c41cd1c65370a94fbbec756d8894947863f42a1f76352c90", size = 5057079, upload-time = "2025-11-25T03:18:16.47Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a4/7d7e423022ab7ca14893312cc9f41121a1c1edc197b1404ac0b489d9647b/pymoo-0.6.1.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9dd7da8b18bffac6b09c75b4a4a645232994d91fc0cdfe39baffdc08fa5d252", size = 5143465, upload-time = "2025-11-25T03:18:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/06/f1/115a03e18820d09107c1aae1029719bd5a42ed9712f8fcf5e6334f44fe8c/pymoo-0.6.1.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f41b359d6de6c9d99ab7da94f115c5aea4d21e74a252bef7c8debec8f20f0b9f", size = 5985143, upload-time = "2025-11-25T03:18:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/ac62bb650197df717bde976b5edf6e46856714900106df0d541f99f5aa0b/pymoo-0.6.1.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ecf1ce515e72d59b78c909aa142d2369cc3c6d771ac7c9162831c0b62a869c8f", size = 6188089, upload-time = "2025-11-25T03:18:27.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/bd/76eecf37a469a2e19c82c85b20402cbeece261ef9d9286189502f39e3566/pymoo-0.6.1.6-cp314-cp314-win_amd64.whl", hash = "sha256:56ecb6f9f5ac0559829e183a23ea3672eae8c1eb9d745305bef98da8d1614d28", size = 1851874, upload-time = "2025-11-25T03:18:28.969Z" }, ] [[package]] @@ -3395,7 +4411,8 @@ name = "pytest" version = "8.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, @@ -3413,12 +4430,15 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -3436,9 +4456,9 @@ dependencies = [ { name = "pygments", marker = "python_full_version >= '3.10'" }, { name = "tomli", marker = "python_full_version == '3.10.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -3448,7 +4468,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "py-cpuinfo" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" } wheels = [ @@ -3457,18 +4477,18 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.14.0", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "pluggy" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +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/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { 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]] @@ -3478,7 +4498,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "9.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ @@ -3543,11 +4563,11 @@ wheels = [ [[package]] name = "pytz" -version = "2025.2" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, ] [[package]] @@ -3651,23 +4671,58 @@ wheels = [ name = "requests" version = "2.32.5" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "charset-normalizer", marker = "python_full_version < '3.10'" }, + { name = "idna", marker = "python_full_version < '3.10'" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.10'" }, + { name = "charset-normalizer", marker = "python_full_version >= '3.10'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +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 = "requests-toolbelt" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "requests" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } wheels = [ @@ -3694,16 +4749,16 @@ wheels = [ [[package]] name = "rich" -version = "14.3.2" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } +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/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, + { 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]] @@ -3720,7 +4775,8 @@ name = "scikit-bio" version = "0.7.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "array-api-compat", version = "1.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -3731,7 +4787,7 @@ dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "patsy", marker = "python_full_version < '3.10'" }, - { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "statsmodels", marker = "python_full_version < '3.10'" }, ] @@ -3766,12 +4822,15 @@ wheels = [ [[package]] name = "scikit-bio" -version = "0.7.1.post1" +version = "0.7.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -3781,52 +4840,52 @@ resolution-markers = [ "python_full_version == '3.10.*'", ] dependencies = [ - { name = "array-api-compat", version = "1.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "array-api-compat", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "biom-format", marker = "python_full_version >= '3.10'" }, { name = "decorator", marker = "python_full_version >= '3.10'" }, - { name = "h5py", version = "3.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "h5py", version = "3.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "natsort", marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "patsy", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "statsmodels", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/d2/7c8d097db3f158cf2f346cc2586943ee3d85263177ddacc7d663d9f95131/scikit_bio-0.7.1.post1.tar.gz", hash = "sha256:cbd92418b711492837ea5ca3a088b540e725bea53a45bc2332b2631afd539f95", size = 5665966, upload-time = "2025-10-30T19:19:43.042Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/38/5fda6a940bb367ca083acaf393cf4f8d4ef9b9fee59a543db7e84af4dfcc/scikit_bio-0.7.1.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68d4bd49c405d1cda4337899b620027ca6090d011ca900576df70ce97d5febfa", size = 6528024, upload-time = "2025-10-30T19:18:50.83Z" }, - { url = "https://files.pythonhosted.org/packages/21/c4/43dc66de1d6699014238928eae7d183e27e5f0838dbaa917711cae95526b/scikit_bio-0.7.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b141af25dd831d729a99dcfbf9410bf395bb4f2a10bd1f8d33390383bba6d3e", size = 6533109, upload-time = "2025-10-30T19:18:52.921Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e7/c0c3a2b93ecc8a076ec0b526a8596b14d8c97d9d669968ba27e0eb2738a9/scikit_bio-0.7.1.post1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6294f4d24cd5916f45a99291ca197497ff63e3068759a5d05b9141135c760d", size = 10774670, upload-time = "2025-10-30T19:18:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/ce/84/618d9eefeb933f0cb080e4d0ce6b561002f7967057a24e52278db067282f/scikit_bio-0.7.1.post1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:683fed27960fb805efc0a2560983035c6e8757a10c033a496c4db46c1f7d207e", size = 10800662, upload-time = "2025-10-30T19:18:57.827Z" }, - { url = "https://files.pythonhosted.org/packages/28/44/570eb1a2bda69f03255b4129521ba4e5e045b590855fcd65dec269ef8ee4/scikit_bio-0.7.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:dd0073860b29561480fc2ef125019e349454497e18dce299d2cbbe93beccd7c6", size = 6447498, upload-time = "2025-10-30T19:18:59.923Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/eb2a2eed239612ec22d69f948cd02a90a86ec0a3eb0ddf468c66e54a8609/scikit_bio-0.7.1.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d0593aa776942aaf27431835698b415d1e9f13cd8cd118f412b8ff4cbc5b8", size = 6537083, upload-time = "2025-10-30T19:19:02.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/c2/de147eef13bfcc19cb086061ef52a6dcb33613b535c5b8b903207a18b1f2/scikit_bio-0.7.1.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22c9d1d48fc037afe2bde68c7db479df177564a17781377e0e0c4a613b5283b9", size = 6535208, upload-time = "2025-10-30T19:19:03.481Z" }, - { url = "https://files.pythonhosted.org/packages/72/2b/68f1ef336a4285b5898a2910d65ebc3f329035228d0fdabce0f1775402b4/scikit_bio-0.7.1.post1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b78d6a0b52f0acc4a28aa3a5a06b8846e147b8404d84d08e18125828f77a55e", size = 11067343, upload-time = "2025-10-30T19:19:04.995Z" }, - { url = "https://files.pythonhosted.org/packages/5c/35/b100d2e6d5e0fd4de4af4c3be9e70c291702afb314b549374331df4f99e4/scikit_bio-0.7.1.post1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddf2f40b99eea14ecd3196e27e1d9f0b7071856edf8782e1b31fcd39b28c8112", size = 11085265, upload-time = "2025-10-30T19:19:07.205Z" }, - { url = "https://files.pythonhosted.org/packages/5c/60/9896f3378ab768af4eb56749bca7eb229be1ef76f6417506fb2437330ed7/scikit_bio-0.7.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:4689b8e47ecd235c71a883c1cbcaad48b51c8c2676fd6d11d4e4134e1bf675fb", size = 6448277, upload-time = "2025-10-30T19:19:09.266Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9e/1709b0eb124cddba864b855df6c428e96c224d99ffb5d008329a5d8caec4/scikit_bio-0.7.1.post1-cp311-cp311-win_arm64.whl", hash = "sha256:593186762927e6248bba3fa20e2656be61c1e3210b0ddefe1428d4e8e01b80fd", size = 6342074, upload-time = "2025-10-30T19:19:10.529Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4e/df25894303407646477823d37409b88e9589a64432bcfd97b5c69e290f01/scikit_bio-0.7.1.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b445ed12f486d06fab36c86d1c858b4baa3978c33b7a4f76d967f6ffb7e1375", size = 6539786, upload-time = "2025-10-30T19:19:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e4/f35635cdf167a0335cae98d6b89da08de4f89c80f740304ba642030761ad/scikit_bio-0.7.1.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0373e14eb37403cfb64de65a047ac11b91b8d83534e98fe9e4404c3d387a25e9", size = 6544189, upload-time = "2025-10-30T19:19:13.261Z" }, - { url = "https://files.pythonhosted.org/packages/cc/5a/25e6fa4db4e0cdbaecf395af06f8d95830ad1e57b0daf176bebe1c957ad6/scikit_bio-0.7.1.post1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd873c615777ab28c448921fc98639897c37ea58412fd704cfe1a96b9bedd0f6", size = 10887784, upload-time = "2025-10-30T19:19:14.829Z" }, - { url = "https://files.pythonhosted.org/packages/78/fd/45a25d9c4ff917582f951f08c2f06ab719802f3d4f9a6dcb4fc291c763f7/scikit_bio-0.7.1.post1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb724ab26b393a074cbb5e9bb47277284a1dc68e70684147a6d0a3ea2d822cdd", size = 10946298, upload-time = "2025-10-30T19:19:17.003Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cd/cea3b6fbe18cd94226e38196e71b4bdb57b187722ade8df63b38a0c15560/scikit_bio-0.7.1.post1-cp312-cp312-win_amd64.whl", hash = "sha256:5e93c64077e815752d7c27b5117ac498b3847a4cf3332aa71a8fcf601ed0fe86", size = 6459262, upload-time = "2025-10-30T19:19:19.349Z" }, - { url = "https://files.pythonhosted.org/packages/d9/61/8dcdd863634f77db5dac566cdd4fe09a01786e42f0ccf06d32e91f68f5a8/scikit_bio-0.7.1.post1-cp312-cp312-win_arm64.whl", hash = "sha256:d86eaeb7b1e101d1d8fa79de2a23ef76697e2252f945884f5ac08f829f0fe1f9", size = 6343827, upload-time = "2025-10-30T19:19:20.77Z" }, - { url = "https://files.pythonhosted.org/packages/fd/96/a8c8bd5cddbc0159f67b8efe399b4f786887608292edd90e57b669dfb58c/scikit_bio-0.7.1.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8269b297ade34ed58939c1f8d5b9506071d2160ce83277f29b938fdad29fbc40", size = 6532159, upload-time = "2025-10-30T19:19:22.223Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e3/679a58a84b5c863b27ba130f3691083f6bb71c3f3142a20a1ebce5f3988f/scikit_bio-0.7.1.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d478ed7858e33d7af9a4bf9c267d46e46eae8194c8ed83fc62f23607bf660f1", size = 6536898, upload-time = "2025-10-30T19:19:24.037Z" }, - { url = "https://files.pythonhosted.org/packages/e7/65/c776d8d32e69d2074bc2eb8c46c7807e1ac572548da9fe950eac249ede4f/scikit_bio-0.7.1.post1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bccf748c80dc9779ed93522513c842473de460e58e76bfa128ddf3cfdea2f604", size = 10848935, upload-time = "2025-10-30T19:19:26.074Z" }, - { url = "https://files.pythonhosted.org/packages/f4/e0/fc497c545850e2e3683e8a30139dccfff0616f02658dfbd6b5f92bf2499d/scikit_bio-0.7.1.post1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ebdd76585f65a3e28f90833c3e4f5fc8241b36c1378bc65abad3330142fb504", size = 10899404, upload-time = "2025-10-30T19:19:27.821Z" }, - { url = "https://files.pythonhosted.org/packages/71/5c/fb855a6f767c88c82bb28ec125457ea8d654b3187aea49bd2696df923513/scikit_bio-0.7.1.post1-cp313-cp313-win_amd64.whl", hash = "sha256:9ec664c8a9da363941570e0a2fc7813d57530b7de2ea1dba71487daaef3be9af", size = 6456865, upload-time = "2025-10-30T19:19:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/84/3d/f7549b21f314b2a23f5a6148e0a17c88d8170bd1599ae8c735f492a01ce5/scikit_bio-0.7.1.post1-cp313-cp313-win_arm64.whl", hash = "sha256:d8890f3f39d12f2fc2cbce2c8b84081d6ccdadf87c870b80e5bf45826b7262b8", size = 6342824, upload-time = "2025-10-30T19:19:31.184Z" }, - { url = "https://files.pythonhosted.org/packages/29/7f/11d1c5e10001870e42f91e0236d6bb3fabd1bd0f6d43eb149fe74596e951/scikit_bio-0.7.1.post1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1bdb3279fa601edc31ecd0b9635ec7dfac594a441124fffafca5abaa3b68ad6c", size = 6530286, upload-time = "2025-10-30T19:19:32.857Z" }, - { url = "https://files.pythonhosted.org/packages/6b/73/60707444c38f8a791046d16c0f54dc4fa98e4d34c01e38e306799a2f0754/scikit_bio-0.7.1.post1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bbd0f0d7928e5f65774b3b63d5581f813989d2f900892864d49aed24cbf6f265", size = 6543599, upload-time = "2025-10-30T19:19:34.188Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e4/5a06cce37472df5503192435637a51ee3a658e4ca7a7c2671f0a4f32737d/scikit_bio-0.7.1.post1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4eca29267f503d9900350013f786d04710f81d0f3254e4a7b6121892ddd202", size = 10855453, upload-time = "2025-10-30T19:19:35.678Z" }, - { url = "https://files.pythonhosted.org/packages/d8/6d/5700bb155429a6aa74ad8ef94a905f52c901fd6de2c7d03b99222e77f1bf/scikit_bio-0.7.1.post1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:286b1f9d39cd23253bc94b7e510bfabc993dcf8be8062b3426afd14e3e0756cc", size = 10849124, upload-time = "2025-10-30T19:19:37.452Z" }, - { url = "https://files.pythonhosted.org/packages/f4/39/05c6f52c53f16b00003099db1ad9b51f037a90382fe7e31c96376896efdc/scikit_bio-0.7.1.post1-cp314-cp314-win_amd64.whl", hash = "sha256:58b7030cfebd3af7444d8f7c1dd2184d296c5f94ba0163ca3e4b62f3e8dd0035", size = 6453898, upload-time = "2025-10-30T19:19:39.581Z" }, - { url = "https://files.pythonhosted.org/packages/ff/95/ff64d260b2dd308338842287b0a165eb3b97e123ead5ff5206862475f812/scikit_bio-0.7.1.post1-cp314-cp314-win_arm64.whl", hash = "sha256:56e63b87fc01f72f247de616069ecd1142cfca5e1c10c2f72611703981841c41", size = 6342299, upload-time = "2025-10-30T19:19:41.73Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/5a/6f/6bb9eda0faccafede38979b79d161f81bb3cbf56ec81dcd7c26fbdadbc4b/scikit_bio-0.7.2.tar.gz", hash = "sha256:d0a991818882c88ca47d9f6441321a440439e1a8a6716a101a931d590c395fe8", size = 5667396, upload-time = "2026-02-12T00:24:30.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/63/ca3236b8c4b7fe638ada64f76a2853343a4296ada72ac2024f3939bd882d/scikit_bio-0.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275f2d4a68d7a8e8567e2228c352d171672fb00ea8dc1c9706138b1b3ebfa462", size = 6465138, upload-time = "2026-02-12T00:23:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1a/de76d17868d23b8e88342d307425c53ccefd2548b3bb974de0bdab89262f/scikit_bio-0.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc7f8811ed236ac33da0c2d542eb601b6a8dc9e13699295ad2eef5b0a376455", size = 6437564, upload-time = "2026-02-12T00:23:34.996Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/5a2dfc7439c00b4f0b50f4c1abb7557128e11883e1b59538744cf49283e3/scikit_bio-0.7.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e756355e1a4745206370690b8182b070642e731a54a5d4d94f86a13c3da8b81a", size = 9830284, upload-time = "2026-02-12T00:23:37.157Z" }, + { url = "https://files.pythonhosted.org/packages/1e/62/3ceef25e854227dcda3dff4df6841a9759bb35f79aee9f8c6b441fb45f57/scikit_bio-0.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2dacce1c86e16378a330d22bb57c42398902f3304033e82009e59d8a9ae1c18", size = 9830204, upload-time = "2026-02-12T00:23:39.43Z" }, + { url = "https://files.pythonhosted.org/packages/8d/81/ec37fce9fdba200bfdbd5782913ec658e83d11b1127557aaec4a0cdd30f7/scikit_bio-0.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:818560d53c2bb3c9d1b6961de3c3884cebcbfee76293b987c0d0f669354c0907", size = 6388666, upload-time = "2026-02-12T00:23:41.524Z" }, + { url = "https://files.pythonhosted.org/packages/36/20/8162865e96b3195ce721379892dc4ec5f81ee4c2c29a68571b7e6a05957c/scikit_bio-0.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a85cb097e755ee9e63a29a1e560fb5bdd9660794fac844d060cf92ae12c864f8", size = 6462098, upload-time = "2026-02-12T00:23:43.138Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6c/7af46e94698bfcdfbd4eabb261e70a8cb2eb69ebed27804e30693082db5a/scikit_bio-0.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709b1f9f5eb8aeeceba4c99663cea79a8ca4e8d4fcabcfeae97c026a50a3322e", size = 6432705, upload-time = "2026-02-12T00:23:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/4b/35/88e97e2d96106be663d1ff10e1c74869bf7972510daa399a8d4c644ab530/scikit_bio-0.7.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f53f1c6871eafab6c5fb831e3a38f3a1dcf2f38eca50a9f9cce9b8abf19620bc", size = 10017556, upload-time = "2026-02-12T00:23:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/de/51/0fa75940de0fe3e0d7a34828831efb6cb3d673c2b23d795da5c74b5cdc7f/scikit_bio-0.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8cd11c45fce6c5c49512ee08787702eed9c0fb97d7cbd0bbf2d09fe2ddcb49c", size = 10021725, upload-time = "2026-02-12T00:23:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/db/63/d3010fdc1486898511b05c9250c96e87a28bb3e584e05c0e0f6e9b4766ed/scikit_bio-0.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:a2751e34c02cba91a434929dd2a3dec1a75f5eb788c3e81c141983063d5af38c", size = 6388577, upload-time = "2026-02-12T00:23:50.567Z" }, + { url = "https://files.pythonhosted.org/packages/83/4d/9a420e15f1554cb983a6d7bde19db74247a29b4bc2f52eb28e9e698e28b5/scikit_bio-0.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:704167b3da4b3677de195ab9cfe4f6888ef7b2a4d25d4d0317162aea79f09476", size = 6285531, upload-time = "2026-02-12T00:23:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3e/533150782121da572c422c7659ead6688deea251900af4072fe8e73628a2/scikit_bio-0.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc5ab62d9a41df60ec4e45d7b0298412cd51d19b05be27c4515c2ac2c427ae88", size = 6471768, upload-time = "2026-02-12T00:23:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/e5/82/068e77d31093a469a44b58a74683c2da7d62f244ce3580bcf09aaeb3f3bb/scikit_bio-0.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9aca4ada9773d227ccff6181f1683993188114a6282abac267404095c9e599f2", size = 6438077, upload-time = "2026-02-12T00:23:55.632Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/74b3b3fef3cec61db3d8467be6e27d079cc8542a700e918c50fa7520c091/scikit_bio-0.7.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9ea5b4ce0047247b9f40bdf0ab2c199f4ac52a8641082071cef67b7f8934ce1", size = 10042987, upload-time = "2026-02-12T00:23:57.081Z" }, + { url = "https://files.pythonhosted.org/packages/6e/68/7a70e65189ea9b4524fe98578ecd38d8c772c191a600a03cb2da5368267f/scikit_bio-0.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07a9b07cdd6311b4eeb7144f263f632d1450408ae057ea66d5631b727bd76798", size = 10101809, upload-time = "2026-02-12T00:23:58.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/ecd5135713cd41ac4ddfebc5f6b381d8075322617f850e92f892d79b31c5/scikit_bio-0.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:b832e087462eb06b802c952da133fc69ab3525448ec7a0b1fdb8535335af1148", size = 6397219, upload-time = "2026-02-12T00:24:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/14/fa/5fca7b5c595f4bc2e38f459917d334e245f5bb9ae694ac7396790898605b/scikit_bio-0.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:ed0bf1ba6a46391a738796591686ca474157a7586a766083bab852793dc02c82", size = 6285552, upload-time = "2026-02-12T00:24:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/3c/53/9c3dc4b736f945be852ae43474a901c06763bba3a5cf52f2adbfcba42bff/scikit_bio-0.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:367bdacd55d38cc7838efc3a76ad6a282e7854a9361ddccdb87dcde4e5697bb6", size = 6466840, upload-time = "2026-02-12T00:24:04.208Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0e/25373b3e62fee797f517edde44d69e7fe99ff8acf6a1faf05839885842c1/scikit_bio-0.7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6c5e223277f1b2f5c25ffe9639c76238b21976a11dd7b0c9473c3ac4df733eb", size = 6433503, upload-time = "2026-02-12T00:24:05.536Z" }, + { url = "https://files.pythonhosted.org/packages/55/00/08d301ee1f091768928e2ec0371aefb4b67f70f718f93e23d145a23353a1/scikit_bio-0.7.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8186a8822374e43826186e55e9db03b51662230075f6a2d48b31c8707fe68e93", size = 9986111, upload-time = "2026-02-12T00:24:07.402Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d2/398ae8185cd8fd59d099128fbb8481f58b596dd6ab29d5dcd14023561e7f/scikit_bio-0.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ced10fd1fb03e1ae99c6d897c320af05e109dc94956ebd45020af167ff2e129", size = 10047817, upload-time = "2026-02-12T00:24:09.409Z" }, + { url = "https://files.pythonhosted.org/packages/e6/20/3c9cbcbe91f1bd881d441da82570857bf302a3987f065ba69b6de03e61fc/scikit_bio-0.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:55a5dd9821e5260764eff392b9e040cded1dccec2b55c3c7c69e7b7d1d73caa9", size = 6394708, upload-time = "2026-02-12T00:24:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e4/628c154fbf83855f9157411bcb80de0b65902ef942318643ff16746fb5aa/scikit_bio-0.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:d5a4838f43bc28b526a9043057678b686f6ac7498ea204c77c4ad26984639fcc", size = 6284476, upload-time = "2026-02-12T00:24:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/62/87/ffc5384a6793ed20f0fd550c6c92cac4eec85763d51d5a1d0e6bb76e37c6/scikit_bio-0.7.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0d44464cfa8ba866d4c839f571a353358cce4f9092961e84e66ca84b4cdb2641", size = 6490423, upload-time = "2026-02-12T00:24:14.128Z" }, + { url = "https://files.pythonhosted.org/packages/05/c6/7b4de57d21e1587df1a9b8a599bb09ed36135a9cbfb57bcce1e695c15c20/scikit_bio-0.7.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ef4115e2b89f2bdf8005eb580336ba6c9b8ae611018b51f01f60c9d6c3f2133d", size = 6460201, upload-time = "2026-02-12T00:24:16.975Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f7/94fed4818f0b616845d89be2ac7149d5afe3ec61df15e77f04811a2b84df/scikit_bio-0.7.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a631f2695980ce35b38c5e8671e70ef9da84e5c115a47297a1ef6016ecd3095", size = 9971402, upload-time = "2026-02-12T00:24:19.943Z" }, + { url = "https://files.pythonhosted.org/packages/da/be/4eeea67c506c563e59e2e472563d9ddbd8bde8ef8c77e0fe326f161c6d36/scikit_bio-0.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54eb5097ebd79821fba548df9df1593dcc1a1499a5630305f8c9e654e741dc26", size = 9998447, upload-time = "2026-02-12T00:24:21.966Z" }, + { url = "https://files.pythonhosted.org/packages/27/c2/01b9072ffac75312902b6007c546c5713fe1084bde27a7479c32bce542dc/scikit_bio-0.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:0e57e93c18309a5b1695186faec40b93d299a5bc85cbad338db90189f142de41", size = 6416146, upload-time = "2026-02-12T00:24:26.479Z" }, + { url = "https://files.pythonhosted.org/packages/46/23/c95e4d0a65fbbf859e40cce4f221291d1d3ddb1ebe03df1b5c0e0c0ed642/scikit_bio-0.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:d938d40fbffc2f486eab7a56487bfdfd5defa3484dcb43e44aea8e7bceececf4", size = 6306354, upload-time = "2026-02-12T00:24:28.869Z" }, ] [[package]] @@ -3834,7 +4893,8 @@ name = "scipy" version = "1.13.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -3928,12 +4988,15 @@ wheels = [ [[package]] name = "scipy" -version = "1.17.0" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version == '3.11.*' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -3942,70 +5005,70 @@ resolution-markers = [ "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, - { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, - { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, - { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, - { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, - { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, - { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, - { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, - { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, - { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, - { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, - { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, - { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, - { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, - { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, - { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, - { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, - { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, - { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, - { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, - { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, - { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, - { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, - { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, - { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, - { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, - { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, - { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, - { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, - { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, - { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, - { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, - { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] @@ -4014,12 +5077,12 @@ version = "0.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "matplotlib", version = "3.10.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } wheels = [ @@ -4031,10 +5094,12 @@ name = "secretstorage" version = "3.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ - { name = "cryptography", marker = "python_full_version < '3.10'" }, + { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, + { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9' and python_full_version < '3.10'" }, { name = "jeepney", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } @@ -4047,13 +5112,14 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version == '3.10.*'", ] dependencies = [ - { name = "cryptography", marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "cryptography", version = "48.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "jeepney", marker = "(python_full_version == '3.10.*' and sys_platform == 'emscripten') or (python_full_version == '3.10.*' and sys_platform == 'win32') or (python_full_version >= '3.10' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } @@ -4063,11 +5129,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] @@ -4093,19 +5159,20 @@ name = "sphinx" version = "7.4.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "babel", marker = "python_full_version < '3.10'" }, { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "imagesize", marker = "python_full_version < '3.10'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "imagesize", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "jinja2", marker = "python_full_version < '3.10'" }, { name = "packaging", marker = "python_full_version < '3.10'" }, { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "snowballstemmer", marker = "python_full_version < '3.10'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" }, { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" }, @@ -4132,11 +5199,11 @@ dependencies = [ { name = "babel", marker = "python_full_version == '3.10.*'" }, { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "imagesize", marker = "python_full_version == '3.10.*'" }, + { name = "imagesize", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "jinja2", marker = "python_full_version == '3.10.*'" }, { name = "packaging", marker = "python_full_version == '3.10.*'" }, { name = "pygments", marker = "python_full_version == '3.10.*'" }, - { name = "requests", marker = "python_full_version == '3.10.*'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.10.*'" }, { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.10.*'" }, @@ -4165,11 +5232,11 @@ dependencies = [ { name = "babel", marker = "python_full_version == '3.11.*'" }, { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "jinja2", marker = "python_full_version == '3.11.*'" }, { name = "packaging", marker = "python_full_version == '3.11.*'" }, { name = "pygments", marker = "python_full_version == '3.11.*'" }, - { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, @@ -4189,9 +5256,12 @@ name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'win32'", - "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", @@ -4201,11 +5271,11 @@ dependencies = [ { name = "babel", marker = "python_full_version >= '3.12'" }, { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "imagesize", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "jinja2", marker = "python_full_version >= '3.12'" }, { name = "packaging", marker = "python_full_version >= '3.12'" }, { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, @@ -4307,6 +5377,74 @@ 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.49" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, + { name = "greenlet", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/76/f908955139842c362aa877848f42f9249642d5b69e06cee9eae5111da1bd/sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f", size = 2159321, upload-time = "2026-04-03T16:50:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/24/e2/17ba0b7bfbd8de67196889b6d951de269e8a46057d92baca162889beb16d/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b", size = 3238937, upload-time = "2026-04-03T16:54:45.731Z" }, + { url = "https://files.pythonhosted.org/packages/90/1e/410dd499c039deacff395eec01a9da057125fcd0c97e3badc252c6a2d6a7/sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1", size = 3237188, upload-time = "2026-04-03T16:56:53.217Z" }, + { url = "https://files.pythonhosted.org/packages/ab/06/e797a8b98a3993ac4bc785309b9b6d005457fc70238ee6cefa7c8867a92e/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339", size = 3190061, upload-time = "2026-04-03T16:54:47.489Z" }, + { url = "https://files.pythonhosted.org/packages/44/d3/5a9f7ef580af1031184b38235da6ac58c3b571df01c9ec061c44b2b0c5a6/sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d", size = 3211477, upload-time = "2026-04-03T16:56:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/69/ec/7be8c8cb35f038e963a203e4fe5a028989167cc7299927b7cf297c271e37/sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3", size = 2119965, upload-time = "2026-04-03T17:00:50.009Z" }, + { url = "https://files.pythonhosted.org/packages/b5/31/0defb93e3a10b0cf7d1271aedd87251a08c3a597ee4f353281769b547b5a/sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75", size = 2142935, upload-time = "2026-04-03T17:00:51.675Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/e3617cc67420f8f403efebd7b043128f94775e57e5b84e7255203390ceae/sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe", size = 2159126, upload-time = "2026-04-03T16:50:13.242Z" }, + { url = "https://files.pythonhosted.org/packages/20/9b/91ca80403b17cd389622a642699e5f6564096b698e7cdcbcbb6409898bc4/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014", size = 3315509, upload-time = "2026-04-03T16:54:49.332Z" }, + { url = "https://files.pythonhosted.org/packages/b1/61/0722511d98c54de95acb327824cb759e8653789af2b1944ab1cc69d32565/sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536", size = 3315014, upload-time = "2026-04-03T16:56:56.376Z" }, + { url = "https://files.pythonhosted.org/packages/46/55/d514a653ffeb4cebf4b54c47bec32ee28ad89d39fafba16eeed1d81dccd5/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88", size = 3267388, upload-time = "2026-04-03T16:54:51.272Z" }, + { url = "https://files.pythonhosted.org/packages/2f/16/0dcc56cb6d3335c1671a2258f5d2cb8267c9a2260e27fde53cbfb1b3540a/sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700", size = 3289602, upload-time = "2026-04-03T16:56:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/51/6c/f8ab6fb04470a133cd80608db40aa292e6bae5f162c3a3d4ab19544a67af/sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a", size = 2119044, upload-time = "2026-04-03T17:00:53.455Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/55a6d627d04b6ebb290693681d7683c7da001eddf90b60cfcc41ee907978/sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af", size = 2143642, upload-time = "2026-04-03T17:00:54.769Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, + { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, + { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, + { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/1d/64/6eb36149b96796ecbc1e2438959d08475e1f8765acbe007f4785a603c39c/sqlalchemy-2.0.49-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43d044780732d9e0381ac8d5316f95d7f02ef04d6e4ef6dc82379f09795d993f", size = 2162373, upload-time = "2026-04-03T16:49:49.55Z" }, + { url = "https://files.pythonhosted.org/packages/b0/96/87e57cfa06af0032a7470660d33e93ad0a2480781bb7705f4312471b993e/sqlalchemy-2.0.49-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d6be30b2a75362325176c036d7fb8d19e8846c77e87683ffaa8177b35135613", size = 3237991, upload-time = "2026-04-03T17:04:07.027Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/0099d0d554313c3587155b60288a9900660afc9989bf382176a5f4d7531b/sqlalchemy-2.0.49-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d898cc2c76c135ef65517f4ddd7a3512fb41f23087b0650efb3418b8389a3cd1", size = 3237313, upload-time = "2026-04-03T17:09:53.187Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9b/a61fcb2e8439a2282e4ac0086bb613e88cd18168cddb358fa2c5790d4705/sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:059d7151fff513c53a4638da8778be7fce81a0c4854c7348ebd0c4078ddf28fe", size = 3187435, upload-time = "2026-04-03T17:04:08.956Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/2165d3f8fa593f20039505af15474f63e85ffd7998afb6218b0fc0cd98e0/sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:334edbcff10514ad1d66e3a70b339c0a29886394892490119dbb669627b17717", size = 3209446, upload-time = "2026-04-03T17:09:55.81Z" }, + { url = "https://files.pythonhosted.org/packages/23/8d/9630ddc9a4db638a7f29954b9e667a4ece41ff65e117460473ca41f06945/sqlalchemy-2.0.49-cp39-cp39-win32.whl", hash = "sha256:74ab4ee7794d7ed1b0c37e7333640e0f0a626fc7b398c07a7aef52f484fddde3", size = 2121680, upload-time = "2026-04-03T16:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5c/480f5d8c737cfb4a494f87de6e0e58a6b6346a0f4db1fa8122c89828e32d/sqlalchemy-2.0.49-cp39-cp39-win_amd64.whl", hash = "sha256:88690f4e1f0fbf5339bedbb127e240fec1fd3070e9934c0b7bef83432f779d2f", size = 2144917, upload-time = "2026-04-03T16:55:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +] + [[package]] name = "statsmodels" version = "0.14.6" @@ -4314,14 +5452,14 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "patsy" }, { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } wheels = [ @@ -4364,56 +5502,56 @@ wheels = [ [[package]] name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +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]] @@ -4434,15 +5572,17 @@ version = "6.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "id" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", version = "8.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, { name = "packaging" }, { name = "readme-renderer" }, - { name = "requests" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "requests-toolbelt" }, { name = "rfc3986" }, { name = "rich" }, - { name = "urllib3" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } wheels = [ @@ -4460,39 +5600,164 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.3" +version = "2026.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.10.*'", +] +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 = "werkzeug" -version = "3.1.5" +version = "3.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + +[[package]] +name = "wrapt" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/d2/387594fb592d027366645f3d7cc9b4d7ca7be93845fbaba6d835a912ef3c/wrapt-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a86d99a14f76facb269dc148590c01aaf47584071809a70da30555228158c", size = 60669, upload-time = "2026-03-06T02:52:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/3f373935bc5509e7ac444c8026a56762e50c1183e7061797437ca96c12ce/wrapt-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a819e39017f95bf7aede768f75915635aa8f671f2993c036991b8d3bfe8dbb6f", size = 61603, upload-time = "2026-03-06T02:54:21.032Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7a/32758ca2853b07a887a4574b74e28843919103194bb47001a304e24af62f/wrapt-2.1.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5681123e60aed0e64c7d44f72bbf8b4ce45f79d81467e2c4c728629f5baf06eb", size = 113632, upload-time = "2026-03-06T02:53:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/eeaa38f670d462e97d978b3b0d9ce06d5b91e54bebac6fbed867809216e7/wrapt-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8b28e97a44d21836259739ae76284e180b18abbb4dcfdff07a415cf1016c3e", size = 115644, upload-time = "2026-03-06T02:54:53.33Z" }, + { url = "https://files.pythonhosted.org/packages/e3/09/2a41506cb17affb0bdf9d5e2129c8c19e192b388c4c01d05e1b14db23c00/wrapt-2.1.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cef91c95a50596fcdc31397eb6955476f82ae8a3f5a8eabdc13611b60ee380ba", size = 112016, upload-time = "2026-03-06T02:54:43.274Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/0e6c3f5e87caadc43db279724ee36979246d5194fa32fed489c73643ba59/wrapt-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dad63212b168de8569b1c512f4eac4b57f2c6934b30df32d6ee9534a79f1493f", size = 114823, upload-time = "2026-03-06T02:54:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/56/b2/0ad17c8248f4e57bedf44938c26ec3ee194715f812d2dbbd9d7ff4be6c06/wrapt-2.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d307aa6888d5efab2c1cde09843d48c843990be13069003184b67d426d145394", size = 111244, upload-time = "2026-03-06T02:54:02.149Z" }, + { url = "https://files.pythonhosted.org/packages/ff/04/bcdba98c26f2c6522c7c09a726d5d9229120163493620205b2f76bd13c01/wrapt-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c87cf3f0c85e27b3ac7d9ad95da166bf8739ca215a8b171e8404a2d739897a45", size = 113307, upload-time = "2026-03-06T02:54:12.428Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1b/5e2883c6bc14143924e465a6fc5a92d09eeabe35310842a481fb0581f832/wrapt-2.1.2-cp310-cp310-win32.whl", hash = "sha256:d1c5fea4f9fe3762e2b905fdd67df51e4be7a73b7674957af2d2ade71a5c075d", size = 57986, upload-time = "2026-03-06T02:54:26.823Z" }, + { url = "https://files.pythonhosted.org/packages/42/5a/4efc997bccadd3af5749c250b49412793bc41e13a83a486b2b54a33e240c/wrapt-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:d8f7740e1af13dff2684e4d56fe604a7e04d6c94e737a60568d8d4238b9a0c71", size = 60336, upload-time = "2026-03-06T02:54:18Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f5/a2bb833e20181b937e87c242645ed5d5aa9c373006b0467bfe1a35c727d0/wrapt-2.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:1c6cc827c00dc839350155f316f1f8b4b0c370f52b6a19e782e2bda89600c7dc", size = 58757, upload-time = "2026-03-06T02:53:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/60c4471fce95afa5922ca09b88a25f03c93343f759aae0f31fb4412a85c7/wrapt-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96159a0ee2b0277d44201c3b5be479a9979cf154e8c82fa5df49586a8e7679bb", size = 60666, upload-time = "2026-03-06T02:52:58.934Z" }, + { url = "https://files.pythonhosted.org/packages/6b/be/80e80e39e7cb90b006a0eaf11c73ac3a62bbfb3068469aec15cc0bc795de/wrapt-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98ba61833a77b747901e9012072f038795de7fc77849f1faa965464f3f87ff2d", size = 61601, upload-time = "2026-03-06T02:53:00.487Z" }, + { url = "https://files.pythonhosted.org/packages/b0/be/d7c88cd9293c859fc74b232abdc65a229bb953997995d6912fc85af18323/wrapt-2.1.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:767c0dbbe76cae2a60dd2b235ac0c87c9cccf4898aef8062e57bead46b5f6894", size = 114057, upload-time = "2026-03-06T02:52:44.08Z" }, + { url = "https://files.pythonhosted.org/packages/ea/25/36c04602831a4d685d45a93b3abea61eca7fe35dab6c842d6f5d570ef94a/wrapt-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c691a6bc752c0cc4711cc0c00896fcd0f116abc253609ef64ef930032821842", size = 116099, upload-time = "2026-03-06T02:54:56.74Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4e/98a6eb417ef551dc277bec1253d5246b25003cf36fdf3913b65cb7657a56/wrapt-2.1.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f3b7d73012ea75aee5844de58c88f44cf62d0d62711e39da5a82824a7c4626a8", size = 112457, upload-time = "2026-03-06T02:53:52.842Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a6/a6f7186a5297cad8ec53fd7578533b28f795fdf5372368c74bd7e6e9841c/wrapt-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:577dff354e7acd9d411eaf4bfe76b724c89c89c8fc9b7e127ee28c5f7bcb25b6", size = 115351, upload-time = "2026-03-06T02:53:32.684Z" }, + { url = "https://files.pythonhosted.org/packages/97/6f/06e66189e721dbebd5cf20e138acc4d1150288ce118462f2fcbff92d38db/wrapt-2.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d7b6fd105f8b24e5bd23ccf41cb1d1099796524bcc6f7fbb8fe576c44befbc9", size = 111748, upload-time = "2026-03-06T02:53:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/ef/43/4808b86f499a51370fbdbdfa6cb91e9b9169e762716456471b619fca7a70/wrapt-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:866abdbf4612e0b34764922ef8b1c5668867610a718d3053d59e24a5e5fcfc15", size = 113783, upload-time = "2026-03-06T02:53:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/91/2c/a3f28b8fa7ac2cefa01cfcaca3471f9b0460608d012b693998cd61ef43df/wrapt-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5a0a0a3a882393095573344075189eb2d566e0fd205a2b6414e9997b1b800a8b", size = 57977, upload-time = "2026-03-06T02:53:27.844Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c3/2b1c7bd07a27b1db885a2fab469b707bdd35bddf30a113b4917a7e2139d2/wrapt-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:64a07a71d2730ba56f11d1a4b91f7817dc79bc134c11516b75d1921a7c6fcda1", size = 60336, upload-time = "2026-03-06T02:54:28.104Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5c/76ece7b401b088daa6503d6264dd80f9a727df3e6042802de9a223084ea2/wrapt-2.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:b89f095fe98bc12107f82a9f7d570dc83a0870291aeb6b1d7a7d35575f55d98a", size = 58756, upload-time = "2026-03-06T02:53:16.319Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, + { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, + { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, + { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, + { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, + { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, + { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, + { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ea/fe375f8a012e5f25b2cd31b093860c8c6540be445345c6f886e5d8bca9ef/wrapt-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5e0fa9cc32300daf9eb09a1f5bdc6deb9a79defd70d5356ba453bcd50aef3742", size = 60661, upload-time = "2026-03-06T02:54:06.572Z" }, + { url = "https://files.pythonhosted.org/packages/d8/2a/0dff969ddf4d3f69f051c8f81afbd3a9fc9fb08ab993b1061ee582b6543c/wrapt-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:710f6e5dfaf6a5d5c397d2d6758a78fecd9649deb21f1b645f5b57a328d63050", size = 61602, upload-time = "2026-03-06T02:53:44.48Z" }, + { url = "https://files.pythonhosted.org/packages/25/62/b80dd7a6c21486a7b8aea63b6bac509b2e4ea184b0eefe3795aa7202a92c/wrapt-2.1.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:305d8a1755116bfdad5dda9e771dcb2138990a1d66e9edd81658816edf51aed1", size = 113340, upload-time = "2026-03-06T02:54:44.626Z" }, + { url = "https://files.pythonhosted.org/packages/82/06/adbe093e07a775d8687cc45329cda9e1b33779357d146c688accbc3a9f1f/wrapt-2.1.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0d8fc30a43b5fe191cf2b1a0c82bab2571dadd38e7c0062ee87d6df858dd06e", size = 115305, upload-time = "2026-03-06T02:53:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dd/31c2596c6bf6bfb1874aa637c66e3028baa83d00708d1439db3b395f8371/wrapt-2.1.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a5d516e22aedb7c9c1d47cba1c63160b1a6f61ec2f3948d127cd38d5cfbb556f", size = 111691, upload-time = "2026-03-06T02:53:17.845Z" }, + { url = "https://files.pythonhosted.org/packages/03/92/e9ba179f4a00b7eb7ab8afc1f729fc3be8bd468b9f1d33be1fd99476493a/wrapt-2.1.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:45914e8efbe4b9d5102fcf0e8e2e3258b83a5d5fba9f8f7b6d15681e9d29ffe0", size = 114507, upload-time = "2026-03-06T02:54:49.398Z" }, + { url = "https://files.pythonhosted.org/packages/0f/dd/5ce1332e824503fb7041a8f8b51ec1f06e7033834e38c01416fa1c599668/wrapt-2.1.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:478282ebd3795a089154fb16d3db360e103aa13d3b2ad30f8f6aac0d2207de0e", size = 110945, upload-time = "2026-03-06T02:54:32.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/17/d1c1d7b63a029205fe8add19db654fd105e2a92a3776c1312e74456ce3ab/wrapt-2.1.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3756219045f73fb28c5d7662778e4156fbd06cf823c4d2d4b19f97305e52819c", size = 113107, upload-time = "2026-03-06T02:54:05.226Z" }, + { url = "https://files.pythonhosted.org/packages/85/9f/aa5b1570ca36a0533ad5fc9d9e436047b9af187f9bd182f5eb6b718fe28b/wrapt-2.1.2-cp39-cp39-win32.whl", hash = "sha256:b8aefb4dbb18d904b96827435a763fa42fc1f08ea096a391710407a60983ced8", size = 57984, upload-time = "2026-03-06T02:53:10.07Z" }, + { url = "https://files.pythonhosted.org/packages/71/3a/a0c92e4c8b6cd8ef179c62249f03f5ce50c142f71fe04c2a14279bd826b4/wrapt-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e5aeab8fe15c3dff75cfee94260dcd9cded012d4ff06add036c28fae7718593b", size = 60334, upload-time = "2026-03-06T02:53:34.183Z" }, + { url = "https://files.pythonhosted.org/packages/75/87/2725632aa7f1f70a9730952444e2ba856bd15ce8ee0210afcdb50f48ab69/wrapt-2.1.2-cp39-cp39-win_arm64.whl", hash = "sha256:f069e113743a21a3defac6677f000068ebb931639f789b5b226598e247a4c89e", size = 58759, upload-time = "2026-03-06T02:53:43.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, ] [[package]] name = "zipp" -version = "3.23.0" +version = "3.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, ]