diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..424beea --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*.py] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml,toml}] +indent_style = space +indent_size = 2 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f265fb7 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Bento runtime paths +BENTO_WORKDIR=/path/to/Bento +BENTO_DATABASES_DIR=/path/to/datasets +BENTO_GLOSA_DIR=/path/to/glosa_v2.2 + +# Optional CLI override +# BENTO_REPO_ROOT=/path/to/Bento diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..4b2d85e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# How to contribute + +We welcome contributions from external contributors, and this document +describes how to merge code changes into this openadmet_models. + +## Getting Started + +* Make sure you have a [GitHub account](https://github.com/signup/free). +* [Fork](https://help.github.com/articles/fork-a-repo/) this repository on GitHub. +* On your local machine, + [clone](https://help.github.com/articles/cloning-a-repository/) your fork of + the repository. + +## Making Changes + +* Add some really awesome code to your local fork. It's usually a [good + idea](http://blog.jasonmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/) + to make changes on a + [branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/) + with the branch name relating to the feature you are going to add. +* When you are ready for others to examine and comment on your new feature, + navigate to your fork of openadmet_models on GitHub and open a [pull + request](https://help.github.com/articles/using-pull-requests/) (PR). Note that + after you launch a PR from one of your fork's branches, all + subsequent commits to that branch will be added to the open pull request + automatically. Each commit added to the PR will be validated for + mergability, compilation and test suite compliance; the results of these tests + will be visible on the PR page. +* If you're providing a new feature, you must add test cases and documentation. +* When the code is ready to go, make sure you run the test suite using pytest. +* When you're ready to be considered for merging, check the "Ready to go" + box on the PR page to let the openadmet_models devs know that the changes are complete. + The code will not be merged until this box is checked, the continuous + integration returns checkmarks, + and multiple core developers give "Approved" reviews. + +# Additional Resources + +* [General GitHub documentation](https://help.github.com/) +* [PR best practices](http://codeinthehole.com/writing/pull-requests-and-other-good-practices-for-teams-using-github/) +* [A guide to contributing to software packages](http://www.contribution-guide.org) +* [Thinkful PR example](http://www.thinkful.com/learn/github-pull-request-tutorial/#Time-to-Submit-Your-First-PR) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..05dc998 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Ruff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: latest + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Sync lint environment + run: uv sync --frozen --no-editable --extra lint + + - name: Ruff format check + run: uv run ruff format --check . + + - name: Ruff lint check + run: uv run ruff check . + + tests: + name: Pytest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: latest + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Sync test environment + run: uv sync --frozen --no-editable --extra test + + - name: Run tests + run: uv run pytest diff --git a/.gitignore b/.gitignore index d270d10..6966439 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,25 @@ # Large files (exceeding GitHub 100MB limit) similarity_scores/pocket_scores.tsv similarity_scores/tanimoto_distances.csv + +# Python +__pycache__/ +*.py[cod] +.pytest_cache/ +.coverage +htmlcov/ +.mypy_cache/ + +# UV / virtual environments +.venv/ +uv.lock.bak + +# Notebook checkpoints +.ipynb_checkpoints/ + +# Runtime outputs +outputs/ +bs/ + +# Local research notes +.thoughts/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ce37f12 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing + +## Development Workflow + +1. Create a feature branch from `main`. +2. Install dependencies with uv: + - `uv sync --no-editable --extra lint --extra test` +3. Run checks locally: + - `uv run ruff check .` + - `uv run ruff format --check .` + - `uv run pytest` +4. Open a pull request with a clear description of the change and validation steps. + +## Coding Standards + +- Use Python 3.10+ compatible syntax. +- Keep scripts and docs in English. +- Prefer small, reviewable commits. +- Include tests for behavior changes. + +## Data and Large Files + +- Do not commit generated artifacts or local runtime caches. +- Keep large benchmark artifacts outside the repository when possible and document retrieval steps in PRs. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..311f74f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 LigandPro Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2f69f4b..e87624a 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,152 @@ # Bento -A comprehensive benchmark for evaluating protein-ligand docking methods, featuring predictions from 20+ state-of-the-art docking algorithms including both machine learning-based and traditional approaches. +Bento is a benchmark repository for evaluating protein-ligand docking methods across curated datasets and prediction outputs. -## Overview +## Highlights -Bento provides a unified framework for analyzing and comparing molecular docking methods on standardized datasets. The benchmark includes predictions from modern ML-based methods (AlphaFold3, Boltz, Chai, DiffDock, NeuralPlexer, etc.) and established classical methods (AutoDock Vina, Smina, Gnina, etc.). +- Unified benchmark assets for ML and classical docking methods. +- Legacy analysis pipeline preserved in `scripts/`. +- UV-first workflow for local development and CI. +- Reproducibility and HPC execution notes. -## Repository Structure +## Repository Layout -``` +```text bento/ -├── annotated_ligands/ # Train and test datasets with ligand annotations -├── annotations/ # Ligand property annotations -│ ├── ligand_classes/ # Ligand classification data (cofactors, saccharides, etc.) -│ ├── buried_frac.json # Buried fraction annotations -│ ├── molecular_weight.json -│ ├── num_atoms.json -│ ├── num_chains.json -│ └── num_rotbonds.json -├── datasets/ # Benchmark datasets (MOAD, PDBbind, Astex, PoseBusters, etc.) -├── predictions_full_raw/ # Raw predictions from all docking methods -├── scripts/ # Analysis and annotation pipeline -└── similarity_scores/ # Pocket and ligand similarity metrics +├── annotated_ligands/ # Train/test tables with ligand-level annotations +├── annotations/ # Annotation maps and ligand class assets +├── datasets/ # Core benchmark datasets +├── predictions_full_raw/ # Raw predictions from docking methods +├── scripts/ # Legacy pipeline scripts (01..04) +├── similarity_scores/ # Similarity score artifacts +├── src/bento/ # Bento CLI wrapper and environment tooling +├── tests/ # Automated tests +├── docs/ # Reproducibility and HPC docs +└── slurm/ # Example SLURM job templates ``` -## Docking Methods Evaluated +## Quick Start (UV) -### ML-Based Methods -- **AlphaFold3** (af3) -- **Boltz** (multiple variants: standard, pocket-based 4Å/10Å) -- **Chai** -- **DiffDock** -- **Matcha** (multiple variants: all-chains, from-true) -- **NeuralPlexer** -- **Uni-Mol** (standard and p2rank variants) +### 1. Sync environment -### Classical Methods -- **AutoDock Vina** (ligand box and p2rank variants) -- **Smina** (ligand box and p2rank variants) -- **Gnina** (ligand box and p2rank variants) -- **FlexAID** (FD) +```bash +uv sync --no-editable +``` -## Benchmark Datasets +`--no-editable` is recommended to guarantee the `bento` console entrypoint is available in all Python 3.12 environments. -- **Astex**: Diverse protein-ligand complexes -- **PDBbind**: Comprehensive binding affinity dataset -- **DockGen**: Generated docking poses -- **PoseBusters**: Challenging docking benchmark -- **MOAD**: Mother of All Databases -- **Timesplit test**: Temporal split for robust evaluation +For development checks: + +```bash +uv sync --no-editable --extra lint --extra test +``` -## Analysis Pipeline +For ligand annotation dependencies: -### 1. Ligand Annotation (`01_generate_ligands_annotation.py`) +```bash +uv sync --no-editable --extra annotation +``` -Generates comprehensive ligand annotations including: -- Chemical class membership (based on SMARTS patterns) -- Peptide-like characteristics -- Aromatic condensed systems -- Molecular properties (MW, atoms, rotatable bonds) +For pocket similarity dependencies (Linux/HPC): -**Usage:** ```bash -python3 scripts/01_generate_ligands_annotation.py \ - --dataset_file datasets/tests.tsv \ - --output_dir outputs/ +uv sync --no-editable --extra similarity ``` -**Requirements:** -- RDKit -- pandas -- pandarallel +### 2. Validate environment -### 2. Pocket Similarity Computation (`02_compute_pockets_similarity.py`) +```bash +uv run --extra annotation bento check-env --profile annotation +uv run bento check-env --profile similarity --glosa-dir /path/to/glosa_v2.2 +``` -Computes binding site similarity using GLoSA (Global-Local Structure Alignment): -- Extracts binding pockets (4.5Å cutoff from ligand) -- Calculates structural similarity scores -- Supports batch processing +### 3. Run pipeline commands -**Requirements:** -- PyMOL -- GLoSA v2.2 -- Java JDK -- g++ compiler +Ligand annotation: -### 3. Annotation Mapping (`03_map_annotations.py`) +```bash +uv run --extra annotation bento annotate-ligands \ + --dataset-file datasets/tests.tsv \ + --output-dir outputs/ +``` -Maps generated annotations to prediction datasets for downstream analysis. +Pocket similarity: -### 4. Visualization (`04_make_subsets_and_draw_plots.ipynb`) +```bash +uv run bento compute-pocket-similarity \ + --data-csv test_run/path_tests.tsv \ + --protein-path path_protein \ + --ligand-path path_ligand \ + --bs-dir bs \ + --glosa-dir /path/to/glosa_v2.2 \ + --output-file similarity_scores/test_pocket_scores.tsv +``` -Jupyter notebook for: -- Creating dataset subsets -- Generating comparative visualizations -- Statistical analysis of docking performance +Annotation mapping: -## Data Notes +```bash +uv run bento map-annotations \ + --tests-file datasets/tests.tsv \ + --annotations-dir annotations \ + --output-tests-file datasets/tests_annotated.tsv \ + --output-tests-exploded-file datasets/tests_exploded_annotated.tsv +``` -### Large Files +## Configuration -The following files exceed GitHub's 100MB limit and are excluded via `.gitignore`: -- `similarity_scores/pocket_scores.tsv` (123 MB) -- `similarity_scores/tanimoto_distances.csv` (562 MB) +Legacy scripts use environment variables: -These files contain comprehensive similarity matrices for all analyzed pockets and ligands. +- `BENTO_WORKDIR`: repository root path (default: current repository root). +- `BENTO_DATABASES_DIR`: root path for external dataset files (default: `BENTO_WORKDIR`). +- `BENTO_GLOSA_DIR`: path to GLoSA directory (default: `/external/glosa`). +- `BENTO_REPO_ROOT`: optional override for CLI location of legacy scripts. -## Configuration +Example: -Edit `scripts/config.py` to set paths: -```python -wrk_dir = '/path/to/bento/' -databases_dir = '/path/to/datasets/' -glosa_path = '/path/to/glosa_v2.2/' +```bash +export BENTO_WORKDIR=/path/to/Bento +export BENTO_DATABASES_DIR=/path/to/datasets +export BENTO_GLOSA_DIR=/path/to/glosa_v2.2 ``` -## Dependencies +## External Tool Requirements -Core requirements: -- Python 3.7+ -- RDKit -- pandas -- pandarallel -- PyMOL (for pocket extraction) -- GLoSA v2.2 (for similarity computation) -- Java JDK (for GLoSA) +Some steps require non-Python tools: -## Citation +- PyMOL (for pocket extraction in script 02; installed with `--extra similarity` + on Linux/HPC). +- Java JDK and `g++` (for GLoSA tooling). +- GLoSA v2.2 executable and `AssignChemicalFeatures` class. -If you use this benchmark in your research, please cite: +Build GLoSA once inside your `BENTO_GLOSA_DIR`: + +```bash +g++ -c glosa.cpp +g++ -o glosa glosa.o +javac AssignChemicalFeatures.java +``` + +These tools are validated by: + +```bash +uv run bento check-env --profile similarity --glosa-dir /path/to/glosa_v2.2 +``` + +## Quality Checks + +```bash +uv run ruff check . +uv run ruff format --check . +uv run pytest +``` + +## Reproducibility and HPC + +- Reproducibility guide: [docs/reproducibility.md](docs/reproducibility.md) +- HPC setup: [docs/hpc.md](docs/hpc.md) +- SLURM templates: `slurm/` + +## Citation ```bibtex @software{bento_benchmark, @@ -141,12 +159,4 @@ If you use this benchmark in your research, please cite: ## License -[Add appropriate license information] - -## Contributing - -Contributions are welcome! Please feel free to submit issues or pull requests. - -## Contact - -For questions or collaboration inquiries, please open an issue on GitHub. +MIT. See [LICENSE](LICENSE). diff --git a/docs/hpc.md b/docs/hpc.md new file mode 100644 index 0000000..8b0ddb6 --- /dev/null +++ b/docs/hpc.md @@ -0,0 +1,75 @@ +# Running Bento on HPC + +This guide provides a practical baseline for SLURM-based execution in a generic HPC environment. + +## 1. Prepare Workspace + +```bash +git clone https://github.com/LigandPro/Bento.git +cd Bento +cp .env.example .env +``` + +Edit `.env` with cluster-specific paths: + +```bash +BENTO_WORKDIR=/path/to/Bento +BENTO_DATABASES_DIR=/path/to/shared/datasets +BENTO_GLOSA_DIR=/path/to/glosa_v2.2 +``` + +## 2. Load Cluster Modules + +Module names can differ by site configuration. Use local equivalents: + +```bash +module purge +module load python/3.12 +module load java/17 +module load gcc +``` + +## 3. Build GLoSA Runtime (One-Time) + +In your `BENTO_GLOSA_DIR`: + +```bash +cd "$BENTO_GLOSA_DIR" +g++ -c glosa.cpp +g++ -o glosa glosa.o +javac AssignChemicalFeatures.java +``` + +## 4. Sync Environment + +```bash +uv sync --frozen --no-editable --extra annotation --extra similarity +``` + +Validate toolchain: + +```bash +uv run --extra annotation bento check-env --profile annotation +uv run bento check-env --profile similarity --glosa-dir "$BENTO_GLOSA_DIR" +``` + +## 5. Submit Jobs + +Example templates are provided in `slurm/`: + +- `slurm/run_annotation.sbatch` +- `slurm/run_similarity.sbatch` + +Submit: + +```bash +sbatch slurm/run_annotation.sbatch +sbatch slurm/run_similarity.sbatch +``` + +## 6. Operational Recommendations + +- Keep `uv.lock` committed and use `uv sync --frozen`. +- Run environment checks before expensive jobs. +- Write outputs to job-specific directories to avoid collisions. +- Capture `git` commit hash and full command line in each job log. diff --git a/docs/reproducibility.md b/docs/reproducibility.md new file mode 100644 index 0000000..73098d7 --- /dev/null +++ b/docs/reproducibility.md @@ -0,0 +1,78 @@ +# Reproducibility Guide + +This document describes a reproducible workflow for Bento using `uv`. + +## Environment + +1. Clone repository. +2. Sync environment with pinned lock: + +```bash +uv sync --frozen --no-editable --extra annotation --extra similarity +``` + +3. Validate runtime: + +```bash +uv run --extra annotation bento check-env --profile annotation +uv run bento check-env --profile similarity --glosa-dir /path/to/glosa_v2.2 +``` + +## Deterministic Inputs + +- Keep the same dataset versions in `datasets/`. +- Keep annotation maps in `annotations/` unchanged for a reproducible run. +- Record `git rev-parse --short HEAD` for each experiment. + +## Pipeline Execution Order + +1. Ligand annotation: + +```bash +uv run --extra annotation bento annotate-ligands \ + --dataset-file datasets/tests.tsv \ + --output-dir outputs/ +``` + +2. Pocket similarity: + +```bash +uv run bento compute-pocket-similarity \ + --data-csv test_run/path_tests.tsv \ + --protein-path path_protein \ + --ligand-path path_ligand \ + --bs-dir bs \ + --glosa-dir /path/to/glosa_v2.2 \ + --output-file similarity_scores/test_pocket_scores.tsv +``` + +3. Mapping: + +```bash +uv run bento map-annotations \ + --tests-file datasets/tests.tsv \ + --annotations-dir annotations \ + --output-tests-file datasets/tests_annotated.tsv \ + --output-tests-exploded-file datasets/tests_exploded_annotated.tsv +``` + +## Recommended Run Metadata + +Store this metadata in each run artifact: + +- Commit SHA. +- Branch name. +- Python version (`uv run python --version`). +- `uv --version`. +- Environment variables: + - `BENTO_WORKDIR` + - `BENTO_DATABASES_DIR` + - `BENTO_GLOSA_DIR` +- Full command lines used for each step. + +## Notes + +- Java and GLoSA are external runtime dependencies for script 02. +- Build `glosa` and `AssignChemicalFeatures.class` inside `BENTO_GLOSA_DIR` before runs. +- PyMOL is installed from `--extra similarity` on Linux/HPC. +- Use `uv run bento check-env --profile similarity --glosa-dir ...` before long jobs. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..953bf5b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,75 @@ +[project] +name = "bento-benchmark" +version = "0.1.0" +description = "Benchmark utilities and analysis pipeline for protein-ligand docking evaluation." +readme = "README.md" +requires-python = ">=3.10,<3.13" +license = { text = "MIT" } +authors = [ + { name = "LigandPro Team" }, +] +dependencies = [ + "numpy>=1.24", + "pandas>=2.0", + "plotly>=5.20", + "statsmodels>=0.14", + "networkx>=3.2", + "tqdm>=4.66", +] + +[project.optional-dependencies] +annotation = [ + "pandarallel>=1.6", + "psutil>=5.9", + "rdkit>=2023.9.6", +] +similarity = [ + "pymol-open-source>=3.2.0a0; platform_system == 'Linux'", +] +lint = [ + "ruff>=0.12.0", +] +test = [ + "pytest>=8.2", + "pytest-cov>=5.0", +] + +[project.scripts] +bento = "bento.cli:main" + +[build-system] +requires = ["hatchling>=1.26"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/bento"] + +[tool.ruff] +target-version = "py310" +line-length = 100 +extend-exclude = [ + "scripts_bckp", + "scripts/04_make_subsets_and_draw_plots.ipynb", +] +lint.select = ["E", "F", "I", "B"] +lint.ignore = [ + "E501", +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["S101"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +addopts = "-q --tb=short" + +[tool.coverage.run] +source = ["src/bento", "scripts"] +omit = ["tests/*"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "if __name__ == .__main__.:", +] diff --git a/scripts/01_generate_ligands_annotation.py b/scripts/01_generate_ligands_annotation.py index beebc91..0ccedcf 100755 --- a/scripts/01_generate_ligands_annotation.py +++ b/scripts/01_generate_ligands_annotation.py @@ -1,4 +1,4 @@ -''' +""" Input: - A dataset of complexes containing a column `path_ligand` with paths to ligand files. @@ -15,18 +15,19 @@ python3 01_generate_ligands_annotation.py \ --dataset_file ../test_run/path_train.tsv \ --output_dir ../test_run/outputs -''' +""" + +import argparse +import os +from collections import defaultdict +from typing import List, Set, Tuple import pandas as pd +import psutil +from config import databases_dir, wrk_dir from pandarallel import pandarallel from rdkit import Chem -from typing import List, Tuple, Set -from collections import defaultdict -import psutil from rdkit.Chem import Descriptors -import os -import argparse -from config import wrk_dir, databases_dir smarts_path = os.path.join(wrk_dir, "annotations/ligand_classes/classes_smarts.csv") SMARTS_DF = pd.read_csv(smarts_path) @@ -36,18 +37,20 @@ print("Pre-compiling SMARTS patterns...") for _, row in SMARTS_DF.iterrows(): try: - compiled_smarts = Chem.MolFromSmarts(row['smarts']) - COMPILED_SMARTS[row['local_group']] = compiled_smarts + compiled_smarts = Chem.MolFromSmarts(row["smarts"]) + COMPILED_SMARTS[row["local_group"]] = compiled_smarts if compiled_smarts is None: print(f"Warning: Failed to compile SMARTS {row['smarts']} for {row['local_group']}") except Exception as e: print(f"Error compiling SMARTS {row['smarts']} for {row['local_group']}: {e}") - COMPILED_SMARTS[row['local_group']] = None -print(f"Compiled {len([v for v in COMPILED_SMARTS.values() if v is not None])} SMARTS patterns successfully") + COMPILED_SMARTS[row["local_group"]] = None +print( + f"Compiled {len([v for v in COMPILED_SMARTS.values() if v is not None])} SMARTS patterns successfully" +) def get_mol(path: str): - ext = path[path.rfind(".") + 1:] + ext = path[path.rfind(".") + 1 :] if ext == "pdb": mol = Chem.MolFromPDBFile(path) elif ext == "sdf": @@ -114,7 +117,9 @@ def _find_peptide_chains(peptide_graph: defaultdict) -> List[List[int]]: return chains -def _get_longest_path_from_unit(peptide_graph: defaultdict, start_unit: int, visited: Set[int]) -> List[int]: +def _get_longest_path_from_unit( + peptide_graph: defaultdict, start_unit: int, visited: Set[int] +) -> List[int]: def _dfs_longest_path(current: int, path: List[int], current_visited: Set[int]) -> List[int]: longest = path[:] for neighbor in peptide_graph[current]: @@ -135,10 +140,7 @@ def _dfs_longest_path(current: int, path: List[int], current_visited: Set[int]) def analyze_peptide_content(mol: Chem.Mol) -> dict: max_length, chains = find_peptide_chain_length(mol) - return { - 'is_peptide_like': max_length >= 2, - 'is_long_peptide': max_length >= 5 - } + return {"is_peptide_like": max_length >= 2, "is_long_peptide": max_length >= 5} def is_aromatic_condensed_system(mol: Chem.Mol, min_rings: int = 2) -> bool: @@ -159,7 +161,7 @@ def is_aromatic_condensed_system(mol: Chem.Mol, min_rings: int = 2) -> bool: condensed_count = 0 for i, ring1 in enumerate(aromatic_rings): - for ring2 in aromatic_rings[i + 1:]: + for ring2 in aromatic_rings[i + 1 :]: if len(set(ring1) & set(ring2)) >= 2: condensed_count += 1 @@ -196,7 +198,7 @@ def process_in_batches(df: pd.DataFrame, batch_size: int = 1000) -> pd.DataFrame total_batches = (len(df) - 1) // batch_size + 1 for i in range(0, len(df), batch_size): - batch = df.iloc[i:i + batch_size].copy() + batch = df.iloc[i : i + batch_size].copy() batch_num = i // batch_size + 1 print(f"Processing batch {batch_num}/{total_batches}") @@ -213,7 +215,11 @@ def process_in_batches(df: pd.DataFrame, batch_size: int = 1000) -> pd.DataFrame def main(): parser = argparse.ArgumentParser() parser.add_argument("--dataset_file", required=True, help="Input dataset TSV file") - parser.add_argument("--output_dir", required=True, help="Output directory to save result file ligand_classes.tsv") + parser.add_argument( + "--output_dir", + required=True, + help="Output directory to save result file ligand_classes.tsv", + ) args = parser.parse_args() os.makedirs(args.output_dir, exist_ok=True) @@ -237,7 +243,7 @@ def main(): print("Cleaning up and saving results...") df = df.drop(columns=["mol"]) output_path = os.path.join(args.output_dir, f"ligand_classes_{suffix}.tsv") - df.to_csv(output_path, index=False, sep='\t') + df.to_csv(output_path, index=False, sep="\t") print(f"Results saved to {output_path}") print("Processing completed successfully!") diff --git a/scripts/02_compute_pockets_similarity.py b/scripts/02_compute_pockets_similarity.py index a678711..8dff8a9 100755 --- a/scripts/02_compute_pockets_similarity.py +++ b/scripts/02_compute_pockets_similarity.py @@ -15,11 +15,17 @@ # ! javac glosa_v2.2/AssignChemicalFeatures.java +import argparse +import itertools import os import subprocess + +import pandas as pd import pymol from pymol import cmd -pymol.finish_launching(['pymol','-qc']) +from tqdm import tqdm + +pymol.finish_launching(["pymol", "-qc"]) def cut_bs( @@ -34,112 +40,155 @@ def cut_bs( cmd.load(protein_path, "ref_protein") cmd.load(ligand_path, "ref_ligand") cmd.select("ref_protein_heavy", "ref_protein and not elem H") - cmd.select("ref_ligand_heavy", "ref_ligand and not elem H") + cmd.select("ref_ligand_heavy", "ref_ligand and not elem H") cmd.select("binding_site", f"byres (ref_protein_heavy within {cutoff} of ref_ligand_heavy)") - + cmd.save(output_path, "binding_site") cmd.delete("all") - - + + def prepare_binding_site( - protein_path: str, - ligand_path: str, + protein_path: str, + ligand_path: str, bs_path: str, ): - os.makedirs(os.path.dirname(bs_path), exist_ok = True) - - if 'pdb' in os.path.basename(bs_path).strip('.pdb'): - print(f"Warning: glosa does not accept 'pdb' substring in filename, rename the file {bs_path}; 'PDB' is acceptable") - - cut_bs(protein_path, ligand_path, bs_path, 4.5) # 4.5 angstrom cutoff - os.system(f"sed -i '/TER/d' {bs_path}") # remove all TER - os.system(f"sed -i 's/^END$/TER\\nEND/' {bs_path}") ## add one TER - os.system(f"sed -i '/^ANISOU/d' {bs_path}") # remove ANISOU - + os.makedirs(os.path.dirname(bs_path), exist_ok=True) + + stem = os.path.splitext(os.path.basename(bs_path))[0].lower() + if "pdb" in stem: + print( + f"Warning: glosa does not accept 'pdb' substring in filename, rename the file {bs_path}; 'PDB' is acceptable" + ) + + cut_bs(protein_path, ligand_path, bs_path, 4.5) # 4.5 angstrom cutoff + + with open(bs_path, "r", encoding="utf-8", errors="ignore") as handle: + lines = handle.readlines() + + filtered = [] + for line in lines: + if line.startswith("TER"): + continue + if line.startswith("ANISOU"): + continue + if line.strip() == "END": + continue + filtered.append(line) + + filtered.append("TER\n") + filtered.append("END\n") + with open(bs_path, "w", encoding="utf-8") as handle: + handle.writelines(filtered) + + def prepare_chem_features(bs_path: str, glosa_dir: str): - subprocess.run(['java', 'AssignChemicalFeatures', - os.path.relpath(bs_path, glosa_dir,)], - shell = False, - cwd = glosa_dir) - + subprocess.run( + [ + "java", + "AssignChemicalFeatures", + os.path.relpath( + bs_path, + glosa_dir, + ), + ], + shell=False, + cwd=glosa_dir, + ) + + def calculate_glosa_score(bs_path_1: str, bs_path_2: str, glosa_dir: str, timeout: float = 500): try: - command_list = ['./glosa', - '-s1', - os.path.relpath(bs_path_1, glosa_dir), - '-s1cf', - os.path.relpath(bs_path_1[:-4] + '-cf.pdb', glosa_dir), - '-s2', - os.path.relpath(bs_path_2, glosa_dir), - '-s2cf', - os.path.relpath(bs_path_1[:-4] + '-cf.pdb', glosa_dir,), - ] - - result = subprocess.run(command_list, shell = False, - cwd = glosa_dir, - capture_output = True, timeout = timeout) - + command_list = [ + "./glosa", + "-s1", + os.path.relpath(bs_path_1, glosa_dir), + "-s1cf", + os.path.relpath(bs_path_1[:-4] + "-cf.pdb", glosa_dir), + "-s2", + os.path.relpath(bs_path_2, glosa_dir), + "-s2cf", + os.path.relpath( + bs_path_2[:-4] + "-cf.pdb", + glosa_dir, + ), + ] + + result = subprocess.run( + command_list, shell=False, cwd=glosa_dir, capture_output=True, timeout=timeout + ) + except Exception as e: - print (f'glosa error for {bs_path_1} {bs_path_2} \n {e} \n') + print(f"glosa error for {bs_path_1} {bs_path_2} \n {e} \n") return - + try: - if result.stdout.decode('utf-8') == '': return - score = result.stdout.decode('utf-8').split('\n')[4] + if result.stdout.decode("utf-8") == "": + return + score = result.stdout.decode("utf-8").split("\n")[4] score = score.split()[1] - if score == '-nan': return + if score == "-nan": + return return float(score) - + except Exception as e: - print (f'glosa error for {bs_path_1} {bs_path_2}\n{e}\n') + print(f"glosa error for {bs_path_1} {bs_path_2}\n{e}\n") return - return + return + if __name__ == "__main__": - - import argparse parser = argparse.ArgumentParser() - parser.add_argument("-output-file", type = str, help = 'file to write calculcated glosa scores csv table') - parser.add_argument("-data-csv", type = str, help = 'filename for pd.dataframe to read') - parser.add_argument("-protein-path", type = str, help = 'column name for protein path') - parser.add_argument("-ligand-path", type = str, help = 'column name for ligand path') - parser.add_argument("-bs-dir", type = str, help = 'folder name to store binding sites') - parser.add_argument("-bs-column", required = False, type = str, help = 'column name for binding site path') - parser.add_argument("-glosa-dir", type = str, help = 'folder with glosa programm') + parser.add_argument( + "-output-file", + required=True, + type=str, + help="file to write calculcated glosa scores csv table", + ) + parser.add_argument( + "-data-csv", required=True, type=str, help="filename for pd.dataframe to read" + ) + parser.add_argument( + "-protein-path", required=True, type=str, help="column name for protein path" + ) + parser.add_argument("-ligand-path", required=True, type=str, help="column name for ligand path") + parser.add_argument( + "-bs-dir", required=True, type=str, help="folder name to store binding sites" + ) + parser.add_argument( + "-bs-column", required=False, type=str, help="column name for binding site path" + ) + parser.add_argument("-glosa-dir", required=True, type=str, help="folder with glosa programm") args = parser.parse_args() - - import pandas as pd - from tqdm import tqdm - import itertools - - df = pd.read_csv(args.data_csv) - print(f'Read {args.data_csv} with {len(df)} rows') + + df = pd.read_csv(args.data_csv, sep=None, engine="python") + print(f"Read {args.data_csv} with {len(df)} rows") if args.bs_column is None: - df['bs_path'] = df[args.protein_path].apply(lambda x: 'bs/'+ os.path.basename(x)) + df["bs_path"] = df[args.protein_path].apply( + lambda x: os.path.join(args.bs_dir, os.path.basename(x)) + ) else: - df['bs_path'] = df[args.bs_column] - - - for _, row in tqdm(df.iterrows(), desc = 'cut out binding pockets', total = len(df)): + df["bs_path"] = df[args.bs_column] + + for _, row in tqdm(df.iterrows(), desc="cut out binding pockets", total=len(df)): if os.path.isfile(row.bs_path): continue prepare_binding_site(row[args.protein_path], row[args.ligand_path], row.bs_path) - - for _, row in tqdm(df.iterrows(), desc = 'prepare chemical features', total = len(df)): - if os.path.isfile(row.bs_path[:-4] + '-cf.pdb'): + + for _, row in tqdm(df.iterrows(), desc="prepare chemical features", total=len(df)): + if os.path.isfile(row.bs_path[:-4] + "-cf.pdb"): continue prepare_chem_features(row.bs_path, args.glosa_dir) scores = [] for bs1, bs2 in tqdm( - itertools.combinations(df.bs_path.tolist(), 2), - ncols = 70, desc = 'calculate scores', - total = int(len(df)*(len(df)-1)/2) ): + itertools.combinations(df.bs_path.tolist(), 2), + ncols=70, + desc="calculate scores", + total=int(len(df) * (len(df) - 1) / 2), + ): score = calculate_glosa_score(bs1, bs2, args.glosa_dir) scores.append([bs1, bs2, score]) - - scores = pd.DataFrame(scores, columns = ['bs1', 'bs2', 'glosa_score']) - scores.to_csv(args.output_file, index = False) - print(f'Written {len(scores)} rows to {args.output_file}') - \ No newline at end of file + scores = pd.DataFrame(scores, columns=["bs1", "bs2", "glosa_score"]) + scores.to_csv(args.output_file, index=False) + print(f"Written {len(scores)} rows to {args.output_file}") diff --git a/scripts/03_map_annotations.py b/scripts/03_map_annotations.py old mode 100755 new mode 100644 index b89386e..350e8f9 --- a/scripts/03_map_annotations.py +++ b/scripts/03_map_annotations.py @@ -1,118 +1,233 @@ -import os, re, glob, json, ast +"""Map ligand annotations to test datasets.""" + +from __future__ import annotations + +import argparse +import ast +import glob +import json +from collections import Counter +from pathlib import Path + import pandas as pd -import numpy as np from tqdm import tqdm + tqdm.pandas() -# --- 1. LOAD DATASETS --- -tests = pd.read_table('datasets/tests.tsv') -tests['ligand'] = tests['ligand'].map(ast.literal_eval).map(tuple) - -# --- 2. ADD ANNOTATIONS (PHYS-CHEM PROPS) --- -for annotation in glob.glob('annotations/*json'): - with open(annotation) as f: - data = json.load(f) - name = os.path.basename(annotation).split('.')[0] - tests[name] = tests['uid'].map(data) - -# --- 3. LOAD ANNOTATIONS (LIGAND CLASSES) --- - -# Saccharide like -with open('annotations/ligand_classes/saccharide_like.json') as f: - saccharide_like = json.load(f) -# Cofactors, manually assembled -with open('annotations/ligand_classes/cofactors.json') as f: - cofactors = json.load(f) -# Modified residues from PDB that are amino acids -with open('annotations/ligand_classes/modres_aa.json') as f: - modres_aa = json.load(f) -# Data about all ligands from PDB -ligands_data = pd.read_table('annotations/ligand_classes/ligands_data.tsv') # keep_default_na=False if you need Sodium (NA) - -# Annotation of train and test ligands by SMARTS patterns -test_annotation = pd.read_csv('annotations/ligand_classes/test_ligand_classes.csv').set_index('uid') -ligand_groups_dict = { - 'aa': ['alpha_amino_acids', 'peptide_like', 'long_peptide'], # amino acid like - 'ster': ['steroids'], # steroids - 'nt': ['pyrimidine-nucleotide', 'purine-nucleotide', # nucleot(z)ide like - 'pyrimidine-nucleozide', 'purine-nucleozide'], - 'sac': ['aldose pyranose', 'ketose pyranose', 'pentose pyranose', # saccaride containing - 'ketose furanose', 'aldose furanose', 'pentose furanose', - 'desoxy-pentose furanose'], - 'macro': ['cycles with >7 members'], # macrocycles with ring > 7 - 'eo': ['at least 3 carbons + metal', 'element_organcs'], # organoelement - 'lip': ['fatty acids/esters (>8 carbons chain)', 'triglyceride (ester)', # lipid like - 'triglyceride (ether)', 'phospholipid', 'lipide-like'], - 'cof': ['hem-like', 'biotin-like', 'B6-like', 'flavin-like', 'FMN-like', # cofactors from cofactors.json - 'nicotin-like', 'quinone-like', 'glutathione-like'], - 'spiro': ['spiro'], # spiro - 'fused': ['condensed_system'] # condensed systems +PROTEIN_3LETTER_CODES = { + "ALA", + "ARG", + "ASN", + "ASP", + "CYS", + "GLN", + "GLU", + "GLY", + "HIS", + "ILE", + "LEU", + "LYS", + "MET", + "PHE", + "PRO", + "SER", + "THR", + "TRP", + "TYR", + "VAL", } -# --- Precompute annotation sets --- -def getAnnotation(df, category): - group_cols = [col for col in ligand_groups_dict[category] if col in df.columns] - if not group_cols: - return pd.Index([]) - return df[df[group_cols].fillna(0).gt(0).any(axis=1)].index - -annotation_sets = { - cat: set(getAnnotation(test_annotation, cat)) - for cat in ligand_groups_dict +LIGAND_GROUPS = { + "aa": ["alpha_amino_acids", "peptide_like", "long_peptide"], + "ster": ["steroids"], + "nt": [ + "pyrimidine-nucleotide", + "purine-nucleotide", + "pyrimidine-nucleozide", + "purine-nucleozide", + ], + "sac": [ + "aldose pyranose", + "ketose pyranose", + "pentose pyranose", + "ketose furanose", + "aldose furanose", + "pentose furanose", + "desoxy-pentose furanose", + ], + "macro": ["cycles with >7 members"], + "eo": ["at least 3 carbons + metal", "element_organcs"], + "lip": [ + "fatty acids/esters (>8 carbons chain)", + "triglyceride (ester)", + "triglyceride (ether)", + "phospholipid", + "lipide-like", + ], + "cof": [ + "hem-like", + "biotin-like", + "B6-like", + "flavin-like", + "FMN-like", + "nicotin-like", + "quinone-like", + "glutathione-like", + ], + "spiro": ["spiro"], + "fused": ["condensed_system"], } -# Cofactors = In cofactors.json or containing words cofactor, ubiquinone... in the ligand name -cofactor_ids = set(cofactors) | set(ligands_data[ligands_data['name'].str.contains('cofactor|ubiquinone|PORPHYRIN|PHEOPHYTIN|CHLOROPHYLL|nicotinamide.*nucleotide|flavin.*nucleotide', case=False)]['ligand_id']) -# Amino acids and peptide like -aa_ids = set(modres_aa) | set(['ACE', 'NH2']) | set(ligands_data[ligands_data['type'] == 'PEPTIDE-LIKE']['ligand_id']) | \ - set([i.upper() for i in protein_letters_1to3_extended.values()]) -# Nucleot(z)ide like -nt_ids = set(ligands_data[ligands_data['pdbx_type'] == 'nucleic acid']['ligand_id']) | \ - set(ligands_data[ligands_data['name'].str.contains('uridin.*phosphate|cytidin.*phosphate|adenin.*phosphate|thymidin.*phosphate|guanosin.*phosphate', case=False)]['ligand_id']) -# Saccaride like -sac_ids = set(ligands_data[(ligands_data['name'].str.contains('L-.*ose$|D-.*ose$', case=False)) & (ligands_data['mw'] < 300)]['ligand_id']) | set(saccharide_like) - -def annotateLigands(ligand, uid): - labels = [] - if ligand in cofactor_ids: - labels.append('cof') - if ligand in aa_ids or uid in annotation_sets['aa']: - labels.append('aa') - if ligand in nt_ids or uid in annotation_sets['nt']: - labels.append('nt') - if ligand in sac_ids: - labels.append('sac') - if uid in annotation_sets['ster']: - labels.append('ster') - if uid in annotation_sets['lip']: - labels.append('lip') - if uid in annotation_sets['macro']: - labels.append('macro') - if uid in annotation_sets['eo']: - labels.append('eo') - - if not labels: - labels.append('other') - - return tuple(labels) - -def getUniqueTypes(ligand_types): + +def _load_json(path: Path) -> dict: + with path.open("r", encoding="utf-8") as handle: + return json.load(handle) + + +def _get_annotation_ids(df: pd.DataFrame, category: str) -> set[str]: + columns = [col for col in LIGAND_GROUPS[category] if col in df.columns] + if not columns: + return set() + selected = df[df[columns].fillna(0).gt(0).any(axis=1)] + return set(selected.index.astype(str)) + + +def _get_unique_types(ligand_types: tuple[tuple[str, ...], ...]) -> tuple[str, ...] | str: counter = Counter(ligand_types) if len(counter) == 1: return next(iter(counter)) most_common = counter.most_common() - return most_common[0][0] if most_common[0][1] > sum(v for _, v in most_common[1:]) else tuple(set(sum(ligand_types, () ))) + if most_common[0][1] > sum(value for _, value in most_common[1:]): + return most_common[0][0] + merged = tuple(sorted(set(sum(ligand_types, ())))) + return merged + + +def main() -> None: + parser = argparse.ArgumentParser(description="Map ligand annotations to tests table.") + parser.add_argument("--tests-file", default="datasets/tests.tsv", help="Input tests TSV file.") + parser.add_argument( + "--annotations-dir", + default="annotations", + help="Annotations root directory.", + ) + parser.add_argument( + "--output-tests-file", + default="datasets/tests_annotated.tsv", + help="Output path for the annotated tests table.", + ) + parser.add_argument( + "--output-tests-exploded-file", + default="datasets/tests_exploded_annotated.tsv", + help="Output path for the exploded annotated tests table.", + ) + parser.add_argument( + "--no-save", + action="store_true", + help="Run mapping without writing output files.", + ) + args = parser.parse_args() + + annotations_dir = Path(args.annotations_dir) + ligand_classes_dir = annotations_dir / "ligand_classes" + + print(f"Reading tests table: {args.tests_file}") + tests = pd.read_table(args.tests_file) + tests["ligand"] = tests["ligand"].map(ast.literal_eval).map(tuple) + + print(f"Loading physicochemical annotation maps from: {annotations_dir}") + for annotation_path in sorted(glob.glob(str(annotations_dir / "*.json"))): + annotation_file = Path(annotation_path) + data = _load_json(annotation_file) + column_name = annotation_file.stem + tests[column_name] = tests["uid"].map(data) + + saccharide_like = _load_json(ligand_classes_dir / "saccharide_like.json") + cofactors = _load_json(ligand_classes_dir / "cofactors.json") + modres_aa = _load_json(ligand_classes_dir / "modres_aa.json") + ligands_data = pd.read_table(ligand_classes_dir / "ligands_data.tsv") + test_annotation = pd.read_csv(ligand_classes_dir / "test_ligand_classes.csv").set_index("uid") + + annotation_sets = { + category: _get_annotation_ids(test_annotation, category) for category in LIGAND_GROUPS + } + + cofactor_ids = set(cofactors) | set( + ligands_data[ + ligands_data["name"].str.contains( + "cofactor|ubiquinone|PORPHYRIN|PHEOPHYTIN|CHLOROPHYLL|nicotinamide.*nucleotide|flavin.*nucleotide", + case=False, + na=False, + ) + ]["ligand_id"] + ) + aa_ids = ( + set(modres_aa) + | {"ACE", "NH2"} + | set(ligands_data[ligands_data["type"] == "PEPTIDE-LIKE"]["ligand_id"]) + | PROTEIN_3LETTER_CODES + ) + nt_ids = set(ligands_data[ligands_data["pdbx_type"] == "nucleic acid"]["ligand_id"]) | set( + ligands_data[ + ligands_data["name"].str.contains( + "uridin.*phosphate|cytidin.*phosphate|adenin.*phosphate|thymidin.*phosphate|guanosin.*phosphate", + case=False, + na=False, + ) + ]["ligand_id"] + ) + saccharide_name_filter = ligands_data["name"].str.contains( + "L-.*ose$|D-.*ose$", case=False, na=False + ) + sac_ids = set( + ligands_data[saccharide_name_filter & (ligands_data["mw"] < 300)]["ligand_id"] + ) | set(saccharide_like) + + def annotate_ligands(ligand_id: str, uid: str) -> tuple[str, ...]: + labels: list[str] = [] + if ligand_id in cofactor_ids: + labels.append("cof") + if ligand_id in aa_ids or uid in annotation_sets["aa"]: + labels.append("aa") + if ligand_id in nt_ids or uid in annotation_sets["nt"]: + labels.append("nt") + if ligand_id in sac_ids: + labels.append("sac") + if uid in annotation_sets["ster"]: + labels.append("ster") + if uid in annotation_sets["lip"]: + labels.append("lip") + if uid in annotation_sets["macro"]: + labels.append("macro") + if uid in annotation_sets["eo"]: + labels.append("eo") + if not labels: + labels.append("other") + return tuple(labels) + + print("Building ligand type annotations...") + tests["ligand_types"] = tests.progress_apply( + lambda row: tuple(annotate_ligands(ligand_id, row["uid"]) for ligand_id in row["ligand"]), + axis=1, + ) + tests["ligand_types_unique"] = tests["ligand_types"].progress_apply(_get_unique_types) + + print("Creating exploded ligand type table...") + tests_exploded = tests.explode("ligand_types_unique", ignore_index=True) + tests_exploded["ligand_types_unique_etc"] = tests_exploded["ligand_types_unique"].map( + lambda value: "etc" if value in {"lip", "ster", "eo"} else value + ) + + if args.no_save: + print("Mapping completed without writing outputs (--no-save).") + return -# --- 4. ADD ANNOTATIONS (LIGAND CLASSES) --- -tests['ligand_types'] = tests.progress_apply(lambda x: tuple(annotateLigands(i, x['uid']) for i in x['ligand']), axis=1) -tests['ligand_types_unique'] = tests['ligand_types'].progress_apply(getUniqueTypes) + print(f"Writing annotated table: {args.output_tests_file}") + tests.to_csv(args.output_tests_file, sep="\t", index=False) + print(f"Writing exploded annotated table: {args.output_tests_exploded_file}") + tests_exploded.to_csv(args.output_tests_exploded_file, sep="\t", index=False) + print("Done.") -# --- 5. EXPLODE TEST --- -tests_exploded = tests.explode('ligand_types_unique', ignore_index=True) -# lipids, steroids and organoelement are sparse so they are united into "etc" -tests_exploded['ligand_types_unique_etc'] = tests_exploded.ligand_types_unique.map(lambda x: 'etc' if x in ['lip', 'ster', 'eo'] else x) -tests_exploded.ligand_types_unique.value_counts() -# --- 6. SAVE ANNOTATED TEST --- -#tests.to_csv('datasets/tests_annotated.tsv', sep='\t', index=False) -#tests_exploded.to_csv('datasets/tests_exploded_annotated.tsv', sep='\t', index=False) \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/scripts/check_env.py b/scripts/check_env.py new file mode 100644 index 0000000..c8be8dd --- /dev/null +++ b/scripts/check_env.py @@ -0,0 +1,10 @@ +"""Compatibility wrapper for environment checks.""" + +from __future__ import annotations + +import sys + +from bento.cli import main + +if __name__ == "__main__": + raise SystemExit(main(["check-env", *sys.argv[1:]])) diff --git a/scripts/config.py b/scripts/config.py old mode 100755 new mode 100644 index be69941..b31238c --- a/scripts/config.py +++ b/scripts/config.py @@ -1,3 +1,21 @@ -wrk_dir = '/mnt/ligandpro/data/marina/razmetka/github/bento/' -databases_dir = '/mnt/ligandpro/db/datasets/' -glosa_path = '/mnt/ligandpro/soft/glosa_v2.2/' +"""Runtime path configuration for Bento legacy scripts.""" + +from __future__ import annotations + +import os +from pathlib import Path + + +def _resolve_repo_root() -> Path: + default_root = Path(__file__).resolve().parents[1] + return Path(os.environ.get("BENTO_WORKDIR", default_root)).resolve() + + +_repo_root = _resolve_repo_root() + +# Historical variable names are kept for compatibility with existing scripts. +wrk_dir = str(_repo_root) +databases_dir = str(Path(os.environ.get("BENTO_DATABASES_DIR", _repo_root)).resolve()) +glosa_path = str( + Path(os.environ.get("BENTO_GLOSA_DIR", _repo_root / "external" / "glosa")).resolve() +) diff --git a/scripts/utills.py b/scripts/utills.py index 3a1fec3..62fc0a1 100755 --- a/scripts/utills.py +++ b/scripts/utills.py @@ -1,11 +1,9 @@ -from statsmodels.stats.proportion import proportions_ztest -from statsmodels.stats.multitest import multipletests import networkx as nx - -import pandas as pd import numpy as np - import plotly.graph_objects as go +from statsmodels.stats.multitest import multipletests +from statsmodels.stats.proportion import proportions_ztest + def getMetrics(df, indices): result = {} @@ -27,20 +25,22 @@ def getMetrics(df, indices): return result -def group_tools_pairwise_cliques(df, subset_ids, alpha=0.05, method='holm'): + +def group_tools_pairwise_cliques(df, subset_ids, alpha=0.05, method="holm"): subset_ids = [i for i in subset_ids if i in df.index] sub = df.loc[subset_ids] tool_list = list(df.columns) m = len(tool_list) - N = len(subset_ids) - + N = len(subset_ids) - successes = np.array([sub[tool].apply(lambda x: bool(x[1])).sum() for tool in tool_list], dtype=int) + successes = np.array( + [sub[tool].apply(lambda x: bool(x[1])).sum() for tool in tool_list], dtype=int + ) proportions = {tool_list[i]: successes[i] / float(N) for i in range(m)} raw_p = np.ones((m, m)) for i in range(m): - for j in range(i+1, m): + for j in range(i + 1, m): count = np.array([successes[i], successes[j]]) nobs = np.array([N, N]) # If N is zero somehow, p=1.0 @@ -54,7 +54,7 @@ def group_tools_pairwise_cliques(df, subset_ids, alpha=0.05, method='holm'): raw_p_flat = raw_p[tri_idx] if raw_p_flat.size == 0: - return [[t] for t in tool_list], proportions, {'raw_p': raw_p, 'p_corrected': raw_p} + return [[t] for t in tool_list], proportions, {"raw_p": raw_p, "p_corrected": raw_p} reject, p_corrected_flat, _, _ = multipletests(raw_p_flat, alpha=alpha, method=method) @@ -66,7 +66,7 @@ def group_tools_pairwise_cliques(df, subset_ids, alpha=0.05, method='holm'): G = nx.Graph() G.add_nodes_from(tool_list) for i in range(m): - for j in range(i+1, m): + for j in range(i + 1, m): if p_corrected[i, j] >= alpha: G.add_edge(tool_list[i], tool_list[j]) @@ -88,43 +88,68 @@ def clique_score(clq): cliques.sort(key=clique_score, reverse=True) chosen = cliques[0] - groups.append(sorted(chosen, key=lambda t: -proportions[t])) + groups.append(sorted(chosen, key=lambda t: -proportions[t])) remaining_nodes -= set(chosen) groups.sort(key=lambda g: -np.mean([proportions[t] for t in g])) - details = {'raw_p': raw_p, 'p_corrected': p_corrected, 'N': N, 'tool_list': tool_list, 'successes': successes} + details = { + "raw_p": raw_p, + "p_corrected": p_corrected, + "N": N, + "tool_list": tool_list, + "successes": successes, + } return groups, proportions, details -def getStatGroups(df, subset_uids, all_tools = False): - if all_tools == False: - df = df.loc[:, ['matcha_fromTrue_fast_40', 'diffdock', - 'gnina_ligand_box', 'vina_ligand_box', 'smina_ligand_box', 'af3', - 'chai', 'boltz_pocket_10A']] + +def getStatGroups(df, subset_uids, all_tools=False): + if not all_tools: + df = df.loc[ + :, + [ + "matcha_fromTrue_fast_40", + "diffdock", + "gnina_ligand_box", + "vina_ligand_box", + "smina_ligand_box", + "af3", + "chai", + "boltz_pocket_10A", + ], + ] groups, props, details = group_tools_pairwise_cliques(df, subset_uids) return groups + def plot_grouped_bars(data, title, w=1500, h=500): # Tool labels converted to LaTeX text tool_labels = { - 'matcha_fromTrue_fast_40': 'Matcha', - 'diffdock': 'DiffDock', - 'unimol_p2rank' : 'Uni-Mol (P2Rank)', #Docking V2 (P2Rank)', - 'gnina_ligand_box' : 'Gnina', - 'vina_ligand_box' : 'Vina', - 'smina_ligand_box' : 'smina', - 'af3' : 'AlphaFold3', - 'chai' : 'Chai-1', - 'neuralplexer' : 'NeuralPLexer', - 'FD': 'FlowDock', - 'boltz_pocket_10A' : "Boltz-2" + "matcha_fromTrue_fast_40": "Matcha", + "diffdock": "DiffDock", + "unimol_p2rank": "Uni-Mol (P2Rank)", # Docking V2 (P2Rank)', + "gnina_ligand_box": "Gnina", + "vina_ligand_box": "Vina", + "smina_ligand_box": "smina", + "af3": "AlphaFold3", + "chai": "Chai-1", + "neuralplexer": "NeuralPLexer", + "FD": "FlowDock", + "boltz_pocket_10A": "Boltz-2", } tol = 0.01 subsets = list(data.keys()) - x_vals, first_vals, second_vals, x_tick_labels, subset_for_bar, tool_for_bar = [], [], [], [], [], [] + x_vals, first_vals, second_vals, x_tick_labels, subset_for_bar, tool_for_bar = ( + [], + [], + [], + [], + [], + [], + ) bar_index = 0 # Flatten data @@ -157,23 +182,37 @@ def plot_grouped_bars(data, title, w=1500, h=500): if subset_tools: max_first_val = max(subset_dict[t][0] for t in subset_tools) max_second_val = max(subset_dict[t][1] for t in subset_tools) - best_first[subset] = {t for t in subset_tools if abs(subset_dict[t][0] - max_first_val) <= tol} - best_second[subset] = {t for t in subset_tools if abs(subset_dict[t][1] - max_second_val) <= tol} + best_first[subset] = { + t for t in subset_tools if abs(subset_dict[t][0] - max_first_val) <= tol + } + best_second[subset] = { + t for t in subset_tools if abs(subset_dict[t][1] - max_second_val) <= tol + } # Bars - for x, f_val, s_val, subset, tool in zip(x_vals, first_vals, second_vals, - subset_for_bar, tool_for_bar): + for x, f_val, s_val, subset, tool in zip( + x_vals, first_vals, second_vals, subset_for_bar, tool_for_bar, strict=False + ): base_color = "#8ba8b7" hatched_color = "#9600ff" if tool in best_first.get(subset, set()) else base_color filled_color = "#9600ff" if tool in best_second.get(subset, set()) else base_color - fig.add_trace(go.Bar( - x=[x], y=[f_val], - marker=dict(color="rgba(0,0,0,0)",line=dict(color=hatched_color, width=1.5),pattern=dict(shape="/", fgcolor=hatched_color)),showlegend=False)) + fig.add_trace( + go.Bar( + x=[x], + y=[f_val], + marker=dict( + color="rgba(0,0,0,0)", + line=dict(color=hatched_color, width=1.5), + pattern=dict(shape="/", fgcolor=hatched_color), + ), + showlegend=False, + ) + ) fig.add_trace(go.Bar(x=[x], y=[s_val], marker=dict(color=filled_color), showlegend=False)) # LaTeX FRACTION LABELS - for x, f_val, s_val in zip(x_vals, first_vals, second_vals): + for x, f_val, s_val in zip(x_vals, first_vals, second_vals, strict=False): f_pct = int(round(f_val * 100)) s_pct = int(round(s_val * 100)) @@ -181,34 +220,66 @@ def plot_grouped_bars(data, title, w=1500, h=500): y_pos = top_height + max_y * 0.02 frac = f"{f_pct}
{s_pct}" - fig.add_annotation(x=x, y=y_pos, text=frac, xref="x", yref="y", showarrow=False, font=dict(size=12, color='grey'), xanchor="center", yanchor="bottom") - + fig.add_annotation( + x=x, + y=y_pos, + text=frac, + xref="x", + yref="y", + showarrow=False, + font=dict(size=12, color="grey"), + xanchor="center", + yanchor="bottom", + ) # Legend (LaTeX text) - fig.add_trace(go.Bar(x=[0], y=[0], - marker=dict(color='white', - pattern=dict(shape='/', fgcolor='#8ba8b7'), - line=dict(color='#8ba8b7', width=1.5)), - name="RMSD ≤ 2Å", - showlegend=True)) - - fig.add_trace(go.Bar(x=[0], y=[0], - marker=dict(color='#8ba8b7'), - name="RMSD ≤ 2Å and PB-valid", - showlegend=True)) - - fig.add_trace(go.Bar(x=[0], y=[0], - marker=dict(color='white', - pattern=dict(shape='/', fgcolor='#9600ff'), - line=dict(color='#9600ff', width=1.5)), - name="Best RMSD ≤ 2Å", - showlegend=True)) - - fig.add_trace(go.Bar(x=[0], y=[0], - marker=dict(color='#9600ff'), - name="Best RMSD ≤ 2Å and PB-valid", - showlegend=True)) + fig.add_trace( + go.Bar( + x=[0], + y=[0], + marker=dict( + color="white", + pattern=dict(shape="/", fgcolor="#8ba8b7"), + line=dict(color="#8ba8b7", width=1.5), + ), + name="RMSD ≤ 2Å", + showlegend=True, + ) + ) + fig.add_trace( + go.Bar( + x=[0], + y=[0], + marker=dict(color="#8ba8b7"), + name="RMSD ≤ 2Å and PB-valid", + showlegend=True, + ) + ) + + fig.add_trace( + go.Bar( + x=[0], + y=[0], + marker=dict( + color="white", + pattern=dict(shape="/", fgcolor="#9600ff"), + line=dict(color="#9600ff", width=1.5), + ), + name="Best RMSD ≤ 2Å", + showlegend=True, + ) + ) + + fig.add_trace( + go.Bar( + x=[0], + y=[0], + marker=dict(color="#9600ff"), + name="Best RMSD ≤ 2Å and PB-valid", + showlegend=True, + ) + ) # Layout with LaTeX axis labels fig.update_layout( @@ -217,31 +288,28 @@ def plot_grouped_bars(data, title, w=1500, h=500): barmode="overlay", bargap=0.2, font=dict(size=18), - xaxis=dict( - tickmode="array",tickvals=x_vals, ticktext=x_tick_labels - ), + xaxis=dict(tickmode="array", tickvals=x_vals, ticktext=x_tick_labels), yaxis=dict( - title='Percent of predictions', + title="Percent of predictions", tickmode="linear", tick0=0, dtick=0.2, tickformat=",.0%", range=[0, max_y * 1.3], ticks="outside", - title_standoff=20 + title_standoff=20, ), - legend=dict( - orientation="h", x=0.5, y=1.25,xanchor="center",yanchor="top" - ), - width=w,height=h,yaxis_title_font=dict(size=18) + legend=dict(orientation="h", x=0.5, y=1.25, xanchor="center", yanchor="top"), + width=w, + height=h, + yaxis_title_font=dict(size=18), ) # Subset labels (LaTeX) cumulative = 0 for subset in subsets: subset_dict, subset_groups = data[subset] - n_bars = sum(len([t for t in subgroup if t in subset_dict]) - for subgroup in subset_groups) + n_bars = sum(len([t for t in subgroup if t in subset_dict]) for subgroup in subset_groups) if n_bars == 0: continue start = cumulative @@ -256,11 +324,11 @@ def plot_grouped_bars(data, title, w=1500, h=500): yref="y", showarrow=False, xanchor="center", - yanchor="bottom" + yanchor="bottom", ) cumulative = end + 2 fig.show() - - #fig.write_image(f"{title}.pdf") - #fig.write_image(f"{title}.jpg") \ No newline at end of file + + # fig.write_image(f"{title}.pdf") + # fig.write_image(f"{title}.jpg") diff --git a/slurm/run_annotation.sbatch b/slurm/run_annotation.sbatch new file mode 100644 index 0000000..59e944e --- /dev/null +++ b/slurm/run_annotation.sbatch @@ -0,0 +1,22 @@ +#!/bin/bash +#SBATCH --job-name=bento-annotate +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err +#SBATCH --time=04:00:00 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=32G + +set -euo pipefail + +cd "${BENTO_WORKDIR:-$PWD}" +mkdir -p logs outputs + +module purge +module load python/3.12 + +uv sync --frozen --no-editable --extra annotation +uv run bento check-env --profile annotation + +uv run --extra annotation bento annotate-ligands \ + --dataset-file datasets/tests.tsv \ + --output-dir outputs/ diff --git a/slurm/run_similarity.sbatch b/slurm/run_similarity.sbatch new file mode 100644 index 0000000..2177be2 --- /dev/null +++ b/slurm/run_similarity.sbatch @@ -0,0 +1,28 @@ +#!/bin/bash +#SBATCH --job-name=bento-similarity +#SBATCH --output=logs/%x-%j.out +#SBATCH --error=logs/%x-%j.err +#SBATCH --time=12:00:00 +#SBATCH --cpus-per-task=8 +#SBATCH --mem=48G + +set -euo pipefail + +cd "${BENTO_WORKDIR:-$PWD}" +mkdir -p logs bs similarity_scores + +module purge +module load python/3.12 +module load java/17 +module load gcc + +uv sync --frozen --no-editable --extra similarity +uv run bento check-env --profile similarity --glosa-dir "${BENTO_GLOSA_DIR:?BENTO_GLOSA_DIR is not set}" + +uv run bento compute-pocket-similarity \ + --data-csv test_run/path_tests.tsv \ + --protein-path path_protein \ + --ligand-path path_ligand \ + --bs-dir bs \ + --glosa-dir "${BENTO_GLOSA_DIR}" \ + --output-file similarity_scores/test_pocket_scores.tsv diff --git a/src/bento/__init__.py b/src/bento/__init__.py new file mode 100644 index 0000000..6f70472 --- /dev/null +++ b/src/bento/__init__.py @@ -0,0 +1,5 @@ +"""Bento benchmark tooling package.""" + +__all__ = ["__version__"] + +__version__ = "0.1.0" diff --git a/src/bento/cli.py b/src/bento/cli.py new file mode 100644 index 0000000..0a1206b --- /dev/null +++ b/src/bento/cli.py @@ -0,0 +1,176 @@ +"""Command-line interface for Bento utilities.""" + +from __future__ import annotations + +import argparse +import os +import subprocess +import sys +from collections.abc import Sequence +from pathlib import Path + +from bento.envcheck import collect_checks, summarize + + +def _repo_root() -> Path: + root_override = os.environ.get("BENTO_REPO_ROOT") + if root_override: + return Path(root_override).resolve() + return Path(__file__).resolve().parents[2] + + +def _legacy_script_path(script_name: str) -> Path: + return _repo_root() / "scripts" / script_name + + +def _run_legacy_script(script_name: str, script_args: Sequence[str]) -> int: + script_path = _legacy_script_path(script_name) + if not script_path.exists(): + print(f"ERROR: script not found: {script_path}", file=sys.stderr) + return 2 + + command = [sys.executable, str(script_path), *script_args] + print(f"Running: {' '.join(command)}") + completed = subprocess.run(command, check=False) + return completed.returncode + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="bento", + description="Bento benchmark CLI wrapper around analysis pipeline scripts.", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + annotate = subparsers.add_parser( + "annotate-ligands", + help="Run ligand annotation pipeline (scripts/01_generate_ligands_annotation.py).", + ) + annotate.add_argument("--dataset-file", required=True, help="Input dataset TSV file.") + annotate.add_argument("--output-dir", required=True, help="Output directory.") + + similarity = subparsers.add_parser( + "compute-pocket-similarity", + help="Run pocket similarity pipeline (scripts/02_compute_pockets_similarity.py).", + ) + similarity.add_argument("--output-file", required=True, help="Output CSV file.") + similarity.add_argument("--data-csv", required=True, help="Input CSV table.") + similarity.add_argument("--protein-path", required=True, help="Protein path column.") + similarity.add_argument("--ligand-path", required=True, help="Ligand path column.") + similarity.add_argument("--glosa-dir", required=True, help="GLoSA directory path.") + similarity.add_argument( + "--bs-dir", + default="bs", + help="Directory where generated binding sites are stored (default: bs).", + ) + similarity.add_argument("--bs-column", help="Existing binding-site column name.") + + map_cmd = subparsers.add_parser( + "map-annotations", + help="Run annotation mapping pipeline (scripts/03_map_annotations.py).", + ) + map_cmd.add_argument( + "--tests-file", + default="datasets/tests.tsv", + help="Input tests TSV file (default: datasets/tests.tsv).", + ) + map_cmd.add_argument( + "--annotations-dir", + default="annotations", + help="Annotations directory (default: annotations).", + ) + map_cmd.add_argument( + "--output-tests-file", + default="datasets/tests_annotated.tsv", + help="Output path for annotated tests table.", + ) + map_cmd.add_argument( + "--output-tests-exploded-file", + default="datasets/tests_exploded_annotated.tsv", + help="Output path for exploded annotated tests table.", + ) + map_cmd.add_argument( + "--no-save", + action="store_true", + help="Run transformations without writing output files.", + ) + + check_env = subparsers.add_parser("check-env", help="Validate required environment components.") + check_env.add_argument( + "--profile", + choices=("annotation", "similarity", "full"), + default="full", + help="Check profile to validate.", + ) + check_env.add_argument( + "--glosa-dir", + help="Optional GLoSA directory to validate glosa binary path.", + ) + + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + + if args.command == "annotate-ligands": + return _run_legacy_script( + "01_generate_ligands_annotation.py", + [ + "--dataset_file", + args.dataset_file, + "--output_dir", + args.output_dir, + ], + ) + + if args.command == "compute-pocket-similarity": + script_args = [ + "-output-file", + args.output_file, + "-data-csv", + args.data_csv, + "-protein-path", + args.protein_path, + "-ligand-path", + args.ligand_path, + "-bs-dir", + args.bs_dir, + "-glosa-dir", + args.glosa_dir, + ] + if args.bs_column: + script_args.extend(["-bs-column", args.bs_column]) + return _run_legacy_script("02_compute_pockets_similarity.py", script_args) + + if args.command == "map-annotations": + script_args = [ + "--tests-file", + args.tests_file, + "--annotations-dir", + args.annotations_dir, + "--output-tests-file", + args.output_tests_file, + "--output-tests-exploded-file", + args.output_tests_exploded_file, + ] + if args.no_save: + script_args.append("--no-save") + return _run_legacy_script("03_map_annotations.py", script_args) + + if args.command == "check-env": + glosa_dir = Path(args.glosa_dir).resolve() if args.glosa_dir else None + results = collect_checks(args.profile, glosa_dir=glosa_dir) + all_ok, lines = summarize(results) + print(f"Environment check profile: {args.profile}") + for line in lines: + print(line) + return 0 if all_ok else 1 + + parser.error(f"Unsupported command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/bento/envcheck.py b/src/bento/envcheck.py new file mode 100644 index 0000000..2b1d232 --- /dev/null +++ b/src/bento/envcheck.py @@ -0,0 +1,88 @@ +"""Environment validation helpers for Bento pipelines.""" + +from __future__ import annotations + +import importlib.util +import shutil +from collections.abc import Iterable +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class CheckResult: + """Single environment check result.""" + + name: str + ok: bool + details: str + + +def _check_module(module_name: str) -> CheckResult: + spec = importlib.util.find_spec(module_name) + if spec is None: + return CheckResult(module_name, False, "module not found") + return CheckResult(module_name, True, "module available") + + +def _check_binary(binary_name: str) -> CheckResult: + found = shutil.which(binary_name) + if found is None: + return CheckResult(binary_name, False, "binary not found in PATH") + return CheckResult(binary_name, True, f"found at {found}") + + +def collect_checks(profile: str, glosa_dir: Path | None = None) -> list[CheckResult]: + """Return checks for a named profile.""" + profile = profile.lower() + module_checks: list[CheckResult] = [] + binary_checks: list[CheckResult] = [] + + if profile in {"annotation", "full"}: + for module_name in ("pandas", "numpy", "rdkit", "pandarallel", "psutil"): + module_checks.append(_check_module(module_name)) + + if profile in {"similarity", "full"}: + for module_name in ("pandas", "tqdm", "pymol"): + module_checks.append(_check_module(module_name)) + for binary_name in ("java", "javac", "g++"): + binary_checks.append(_check_binary(binary_name)) + if glosa_dir is not None: + glosa_binary = glosa_dir / "glosa" + if glosa_binary.exists(): + binary_checks.append(CheckResult("glosa", True, f"found at {glosa_binary}")) + else: + binary_checks.append( + CheckResult("glosa", False, f"not found at expected path {glosa_binary}") + ) + assign_features_class = glosa_dir / "AssignChemicalFeatures.class" + if assign_features_class.exists(): + binary_checks.append( + CheckResult( + "AssignChemicalFeatures.class", + True, + f"found at {assign_features_class}", + ) + ) + else: + binary_checks.append( + CheckResult( + "AssignChemicalFeatures.class", + False, + "not found; compile AssignChemicalFeatures.java with javac in glosa dir", + ) + ) + + return [*module_checks, *binary_checks] + + +def summarize(results: Iterable[CheckResult]) -> tuple[bool, list[str]]: + """Return pass/fail and human-readable lines.""" + lines: list[str] = [] + all_ok = True + for result in results: + status = "OK" if result.ok else "MISSING" + lines.append(f"[{status}] {result.name}: {result.details}") + if not result.ok: + all_ok = False + return all_ok, lines diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..39e78b7 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from bento import cli + + +def test_annotate_ligands_dispatch(monkeypatch): + calls = {} + + def fake_run(script_name, script_args): + calls["script_name"] = script_name + calls["script_args"] = script_args + return 0 + + monkeypatch.setattr(cli, "_run_legacy_script", fake_run) + exit_code = cli.main( + [ + "annotate-ligands", + "--dataset-file", + "datasets/tests.tsv", + "--output-dir", + "outputs", + ] + ) + + assert exit_code == 0 + assert calls["script_name"] == "01_generate_ligands_annotation.py" + assert calls["script_args"] == [ + "--dataset_file", + "datasets/tests.tsv", + "--output_dir", + "outputs", + ] + + +def test_compute_similarity_dispatch(monkeypatch): + calls = {} + + def fake_run(script_name, script_args): + calls["script_name"] = script_name + calls["script_args"] = script_args + return 0 + + monkeypatch.setattr(cli, "_run_legacy_script", fake_run) + exit_code = cli.main( + [ + "compute-pocket-similarity", + "--output-file", + "scores.csv", + "--data-csv", + "input.csv", + "--protein-path", + "path_protein", + "--ligand-path", + "path_ligand", + "--bs-dir", + "bs", + "--glosa-dir", + "/opt/glosa", + "--bs-column", + "bs_col", + ] + ) + + assert exit_code == 0 + assert calls["script_name"] == "02_compute_pockets_similarity.py" + assert "-bs-column" in calls["script_args"] + + +def test_check_env_returns_status(): + exit_code = cli.main(["check-env", "--profile", "annotation"]) + assert exit_code in {0, 1} diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..afe8f4f --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import importlib.util +from pathlib import Path + + +def _load_config_module(): + config_path = Path(__file__).resolve().parents[1] / "scripts" / "config.py" + spec = importlib.util.spec_from_file_location("bento_legacy_config", config_path) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def test_config_defaults(monkeypatch): + monkeypatch.delenv("BENTO_WORKDIR", raising=False) + monkeypatch.delenv("BENTO_DATABASES_DIR", raising=False) + monkeypatch.delenv("BENTO_GLOSA_DIR", raising=False) + + module = _load_config_module() + expected_root = str((Path(__file__).resolve().parents[1]).resolve()) + + assert module.wrk_dir == expected_root + assert module.databases_dir == expected_root + assert module.glosa_path.endswith("external/glosa") + + +def test_config_env_override(monkeypatch, tmp_path): + workdir = tmp_path / "workdir" + dbdir = tmp_path / "dbdir" + glosa = tmp_path / "glosa" + workdir.mkdir() + dbdir.mkdir() + glosa.mkdir() + + monkeypatch.setenv("BENTO_WORKDIR", str(workdir)) + monkeypatch.setenv("BENTO_DATABASES_DIR", str(dbdir)) + monkeypatch.setenv("BENTO_GLOSA_DIR", str(glosa)) + + module = _load_config_module() + + assert module.wrk_dir == str(workdir.resolve()) + assert module.databases_dir == str(dbdir.resolve()) + assert module.glosa_path == str(glosa.resolve()) diff --git a/tests/test_envcheck.py b/tests/test_envcheck.py new file mode 100644 index 0000000..35a73e5 --- /dev/null +++ b/tests/test_envcheck.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from pathlib import Path + +from bento import envcheck + + +def _patch_dependency_checks(monkeypatch): + monkeypatch.setattr( + envcheck, + "_check_module", + lambda module_name: envcheck.CheckResult(module_name, True, "module available"), + ) + monkeypatch.setattr( + envcheck, + "_check_binary", + lambda binary_name: envcheck.CheckResult(binary_name, True, "binary available"), + ) + + +def _index_results(results: list[envcheck.CheckResult]) -> dict[str, envcheck.CheckResult]: + return {result.name: result for result in results} + + +def test_similarity_profile_requires_assign_features_class(tmp_path: Path, monkeypatch): + _patch_dependency_checks(monkeypatch) + glosa_dir = tmp_path / "glosa_v2.2" + glosa_dir.mkdir() + (glosa_dir / "glosa").write_text("", encoding="utf-8") + + results = envcheck.collect_checks("similarity", glosa_dir=glosa_dir) + indexed = _index_results(results) + + assert indexed["glosa"].ok is True + assert indexed["AssignChemicalFeatures.class"].ok is False + assert "compile AssignChemicalFeatures.java" in indexed["AssignChemicalFeatures.class"].details + + +def test_similarity_profile_passes_when_assign_features_class_exists(tmp_path: Path, monkeypatch): + _patch_dependency_checks(monkeypatch) + glosa_dir = tmp_path / "glosa_v2.2" + glosa_dir.mkdir() + (glosa_dir / "glosa").write_text("", encoding="utf-8") + (glosa_dir / "AssignChemicalFeatures.class").write_bytes(b"class") + + results = envcheck.collect_checks("similarity", glosa_dir=glosa_dir) + indexed = _index_results(results) + + assert indexed["glosa"].ok is True + assert indexed["AssignChemicalFeatures.class"].ok is True diff --git a/tests/test_map_annotations.py b/tests/test_map_annotations.py new file mode 100644 index 0000000..2a36d8f --- /dev/null +++ b/tests/test_map_annotations.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + + +def test_map_annotations_script_smoke(tmp_path): + tests_tsv = tmp_path / "tests.tsv" + annotations_dir = tmp_path / "annotations" + ligand_classes_dir = annotations_dir / "ligand_classes" + ligand_classes_dir.mkdir(parents=True) + + tests_tsv.write_text("uid\tligand\nu1\t('ATP',)\n", encoding="utf-8") + + (annotations_dir / "molecular_weight.json").write_text('{"u1": 507.0}\n', encoding="utf-8") + (ligand_classes_dir / "saccharide_like.json").write_text("[]\n", encoding="utf-8") + (ligand_classes_dir / "cofactors.json").write_text("[]\n", encoding="utf-8") + (ligand_classes_dir / "modres_aa.json").write_text("[]\n", encoding="utf-8") + (ligand_classes_dir / "ligands_data.tsv").write_text( + "ligand_id\tname\ttype\tpdbx_type\tmw\nATP\tATP\tOTHER\tnucleic acid\t507\n", + encoding="utf-8", + ) + (ligand_classes_dir / "test_ligand_classes.csv").write_text( + "uid,alpha_amino_acids\nu1,0\n", + encoding="utf-8", + ) + + output_tests = tmp_path / "tests_annotated.tsv" + output_exploded = tmp_path / "tests_exploded_annotated.tsv" + + script_path = Path(__file__).resolve().parents[1] / "scripts" / "03_map_annotations.py" + result = subprocess.run( + [ + sys.executable, + str(script_path), + "--tests-file", + str(tests_tsv), + "--annotations-dir", + str(annotations_dir), + "--output-tests-file", + str(output_tests), + "--output-tests-exploded-file", + str(output_exploded), + ], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + assert output_tests.exists() + assert output_exploded.exists() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..53b62a8 --- /dev/null +++ b/uv.lock @@ -0,0 +1,775 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.13" +resolution-markers = [ + "python_full_version >= '3.11' and sys_platform == 'win32'", + "python_full_version >= '3.11' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "bento-benchmark" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", 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.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "plotly" }, + { name = "statsmodels" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +annotation = [ + { name = "pandarallel" }, + { name = "psutil" }, + { name = "rdkit" }, +] +lint = [ + { name = "ruff" }, +] +similarity = [ + { name = "pymol-open-source", marker = "sys_platform == 'linux'" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "networkx", specifier = ">=3.2" }, + { name = "numpy", specifier = ">=1.24" }, + { name = "pandarallel", marker = "extra == 'annotation'", specifier = ">=1.6" }, + { name = "pandas", specifier = ">=2.0" }, + { name = "plotly", specifier = ">=5.20" }, + { name = "psutil", marker = "extra == 'annotation'", specifier = ">=5.9" }, + { name = "pymol-open-source", marker = "sys_platform == 'linux' and extra == 'similarity'", specifier = ">=3.2.0a0" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.2" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=5.0" }, + { name = "rdkit", marker = "extra == 'annotation'", specifier = ">=2023.9.6" }, + { name = "ruff", marker = "extra == 'lint'", specifier = ">=0.12.0" }, + { name = "statsmodels", specifier = ">=0.14" }, + { name = "tqdm", specifier = ">=4.66" }, +] +provides-extras = ["annotation", "similarity", "lint", "test"] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/d4/7827d9ffa34d5d4d752eec907022aa417120936282fc488306f5da08c292/coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415", size = 219152, upload-time = "2026-02-09T12:56:11.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b0/d69df26607c64043292644dbb9dc54b0856fabaa2cbb1eeee3331cc9e280/coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b", size = 219667, upload-time = "2026-02-09T12:56:13.33Z" }, + { url = "https://files.pythonhosted.org/packages/82/a4/c1523f7c9e47b2271dbf8c2a097e7a1f89ef0d66f5840bb59b7e8814157b/coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a", size = 246425, upload-time = "2026-02-09T12:56:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/f8/02/aa7ec01d1a5023c4b680ab7257f9bfde9defe8fdddfe40be096ac19e8177/coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f", size = 248229, upload-time = "2026-02-09T12:56:16.31Z" }, + { url = "https://files.pythonhosted.org/packages/35/98/85aba0aed5126d896162087ef3f0e789a225697245256fc6181b95f47207/coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012", size = 250106, upload-time = "2026-02-09T12:56:18.024Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1db59bd67494bc162e3e4cd5fbc7edba2c7026b22f7c8ef1496d58c2b94c/coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def", size = 252021, upload-time = "2026-02-09T12:56:19.272Z" }, + { url = "https://files.pythonhosted.org/packages/9d/97/72899c59c7066961de6e3daa142d459d47d104956db43e057e034f015c8a/coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256", size = 247114, upload-time = "2026-02-09T12:56:21.051Z" }, + { url = "https://files.pythonhosted.org/packages/39/1f/f1885573b5970235e908da4389176936c8933e86cb316b9620aab1585fa2/coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda", size = 248143, upload-time = "2026-02-09T12:56:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/a8/cf/e80390c5b7480b722fa3e994f8202807799b85bc562aa4f1dde209fbb7be/coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92", size = 246152, upload-time = "2026-02-09T12:56:23.748Z" }, + { url = "https://files.pythonhosted.org/packages/44/bf/f89a8350d85572f95412debb0fb9bb4795b1d5b5232bd652923c759e787b/coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c", size = 249959, upload-time = "2026-02-09T12:56:25.209Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6e/612a02aece8178c818df273e8d1642190c4875402ca2ba74514394b27aba/coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58", size = 246416, upload-time = "2026-02-09T12:56:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/b5afc39af67c2fa6786b03c3a7091fc300947387ce8914b096db8a73d67a/coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9", size = 247025, upload-time = "2026-02-09T12:56:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/51/30/2bba8ef0682d5bd210c38fe497e12a06c9f8d663f7025e9f5c2c31ce847d/coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf", size = 221758, upload-time = "2026-02-09T12:56:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/331f94934cf6c092b8ea59ff868eb587bc8fe0893f02c55bc6c0183a192e/coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95", size = 222693, upload-time = "2026-02-09T12:56:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[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 = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "narwhals" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11' and sys_platform == 'win32'", + "python_full_version >= '3.11' and sys_platform == 'emscripten'", + "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11' and sys_platform == 'win32'", + "python_full_version >= '3.11' and sys_platform == 'emscripten'", + "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/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" }, +] + +[[package]] +name = "packaging" +version = "26.0" +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" } +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" }, +] + +[[package]] +name = "pandarallel" +version = "1.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/c5/787365399cc7262e20d1d9f42ba202018c2191e6cd5b1a2a10f9161dae35/pandarallel-1.6.5.tar.gz", hash = "sha256:1c2df98ff6441e8ae13ff428ceebaa7ec42d731f7f972c41ce4fdef1d3adf640", size = 14201, upload-time = "2023-05-02T20:43:15.214Z" } + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11' and sys_platform == 'win32'", + "python_full_version >= '3.11' and sys_platform == 'emscripten'", + "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 = "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/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, +] + +[[package]] +name = "patsy" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { 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/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +] + +[[package]] +name = "plotly" +version = "6.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/fb/41efe84970cfddefd4ccf025e2cbfafe780004555f583e93dba3dac2cdef/plotly-6.6.0.tar.gz", hash = "sha256:b897f15f3b02028d69f755f236be890ba950d0a42d7dfc619b44e2d8cea8748c", size = 7027956, upload-time = "2026-03-02T21:10:25.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl", hash = "sha256:8d6daf0f87412e0c0bfe72e809d615217ab57cc715899a1e5145135a7800d1d0", size = 9910315, upload-time = "2026-03-02T21:10:18.131Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pymol-open-source" +version = "3.2.0a0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/6d/976be1405c1ead4514ddcc2b5306fe64e3630f5dec1dc2cc7e4e85276b2f/pymol_open_source-3.2.0a0-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:878bcaba7ff18b8bf95707694379d3b85b0b1cd655ef5895c26babb6d2d38954", size = 31358062, upload-time = "2025-08-04T14:39:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/5d5d400bd04264f2114c76b1843ce0fd17b3a50706d1862c4dec0920c691/pymol_open_source-3.2.0a0-cp311-cp311-manylinux_2_35_x86_64.whl", hash = "sha256:2cb05c1f0584014bf2fa1943388fee297f1e9953b2cdc80945735650822b5164", size = 31358077, upload-time = "2025-08-04T14:39:52.714Z" }, + { url = "https://files.pythonhosted.org/packages/04/df/82cb271593ce617a6355d330c31500a2f747eca4fadd0e5466ff3a69ae69/pymol_open_source-3.2.0a0-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:6702a33ca3f23197d1ab026ecbdcc218472c9fcfe92782fdb146efcfc1c82601", size = 31358340, upload-time = "2025-08-04T14:39:59.009Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +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" } +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" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2026.1.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, +] + +[[package]] +name = "rdkit" +version = "2025.9.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/cd/864924bb0606c8d90869f1f460f7a5b6911e98d67d56ad404b5eab1852b3/rdkit-2025.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce558b4f8e63a52ec932b59e867a8e1b2e3cde304e17b652cd54e4f73a6f5607", size = 29513135, upload-time = "2026-03-02T18:08:24.822Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9d/3d4d367e5ad6c995d043adc6382ea82480b1b8b4aba2c6a730ddd5d21abf/rdkit-2025.9.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5aedf8a713998553e17b75a09990561a7824a542c85b48987d9bc88dbd1329f0", size = 35244087, upload-time = "2026-03-02T18:08:29.612Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ba/761a33df54a935a20d5ee85e6a9c6334f509a2d4e1b1fcb7d382ab731835/rdkit-2025.9.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6d6746cea16ce048259bf463a5beef92cf130fd0f8f62a999d4eb28384e7831a", size = 36730482, upload-time = "2026-03-02T18:08:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1d/1851932f47b08bc203c373e4102b552ed031eaf596444a5a98eff5f67a83/rdkit-2025.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:a961a99f91f1d4366d8eaf53e0648c36e3f6dd96a040be9fc6b90b4a7133dae8", size = 24297586, upload-time = "2026-03-02T18:08:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fa/61735db51afc02c2c6d1d203dc5b0ddade8bb951fe72672f463cd111a77b/rdkit-2025.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8348ec9c5a223d94fa848f8330a68f746d870f61540a846fe8ea356998ea77f1", size = 29513218, upload-time = "2026-03-02T18:08:41.492Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fa/5d5919b3ef2f7f99419de3d5075977594cacf97effb60ca7000450372084/rdkit-2025.9.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d0ec921fee7f2908e1da8c4ea1185dbb6d0a22787bcf9749b442691c121c02ef", size = 35239409, upload-time = "2026-03-02T18:08:45.413Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/0c4656224167261093d057d018acb8b9ddbad22f3ebe3706424e23ef7b6b/rdkit-2025.9.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3f4fc084890efb29b51ea4679bb07d28b276b6e73e3381e678a5ba057b4c4222", size = 36729354, upload-time = "2026-03-02T18:08:50.948Z" }, + { url = "https://files.pythonhosted.org/packages/51/6d/137748016222aee73e0d97b515838ee38dc5ed62f528e892c7d424994a09/rdkit-2025.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:8c8e592948f442d2ec5a34f2e31295e03778686cec459a3d23c41b46ac56a358", size = 24297670, upload-time = "2026-03-02T18:08:55.013Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/599c81d456ab2eb3e8d6af1f2f8275689eec3a7c836c3ca6e4c75fb98607/rdkit-2025.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:95350eeb5d70c5576eca748caf59106ebeea259278ff05c3812adb12c5c4dc10", size = 29558443, upload-time = "2026-03-02T18:08:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5f/54a2d040d5ec2f21182da61d67145dd6b0cc3abf2db6737368b8bb3883ef/rdkit-2025.9.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:7e8cda5243d586356c2a66bdf70fea3cb3fd552200556efe375da64efb047f73", size = 35119461, upload-time = "2026-03-02T18:09:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/43/8c/42dde9df1a665e3589eeeaea463b11ec89c9d0c0e82db22d8128e3e14490/rdkit-2025.9.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6be95b735c428ba27badf39e92078133d3a505f872d0a31467ba577d73c57676", size = 36664387, upload-time = "2026-03-02T18:09:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/69077326d9969bbf7958b7f6ef03a3111de83ddf8800d5c7dc390041bf36/rdkit-2025.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:80c1fe3d1cc88540f2495272f32f02b4ec58a73db53649c9e4249087deeb62dc", size = 24315769, upload-time = "2026-03-02T18:09:11.987Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11' and sys_platform == 'win32'", + "python_full_version >= '3.11' and sys_platform == 'emscripten'", + "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/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" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "statsmodels" +version = "0.14.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", 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.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "patsy" }, + { name = "scipy", version = "1.15.3", 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 = [ + { url = "https://files.pythonhosted.org/packages/b5/6d/9ec309a175956f88eb8420ac564297f37cf9b1f73f89db74da861052dc29/statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0", size = 10142419, upload-time = "2025-12-05T19:27:35.625Z" }, + { url = "https://files.pythonhosted.org/packages/86/8f/338c5568315ec5bf3ac7cd4b71e34b98cb3b0f834919c0c04a0762f878a1/statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9", size = 10022819, upload-time = "2025-12-05T19:27:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/5fc4cbc2d608f9b483b0675f82704a8bcd672962c379fe4d82100d388dbf/statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f", size = 10118927, upload-time = "2025-12-05T23:07:51.256Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/b86c861c32186403fe121d9ab27bc16d05839b170d92a978beb33abb995e/statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9", size = 10413015, upload-time = "2025-12-05T23:08:53.95Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/daf0dba729ccdc4176605f4a0fd5cfe71cdda671749dca10e74a732b8b1c/statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8", size = 10441248, upload-time = "2025-12-05T23:09:09.353Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1c/2e10b7c7cc44fa418272996bf0427b8016718fd62f995d9c1f7ab37adf35/statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea", size = 9583410, upload-time = "2025-12-05T19:28:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514, upload-time = "2025-12-05T19:28:16.521Z" }, + { url = "https://files.pythonhosted.org/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346, upload-time = "2025-12-05T19:28:29.568Z" }, + { url = "https://files.pythonhosted.org/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872, upload-time = "2025-12-05T23:09:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964, upload-time = "2025-12-05T23:09:41.326Z" }, + { url = "https://files.pythonhosted.org/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611, upload-time = "2025-12-05T23:09:57.131Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385, upload-time = "2025-12-05T19:28:42.778Z" }, + { url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" }, + { url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" }, +] + +[[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/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +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" } +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" }, +]