diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..8bb8e28 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1 @@ +FROM mcr.microsoft.com/devcontainers/anaconda:1-3 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..4dda942 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// Small devcontainer which loads anaconda. All postinstallation steps have to be done manually. +// This comes with snakemake and docker-in-docker. + +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/anaconda +{ + "name": "Anaconda (Python 3)", + "build": { + "context": "..", + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + // For yamlfmt + "ghcr.io/devcontainers/features/go:1": {}, + // For web display + "ghcr.io/devcontainers/features/node:1": {}, + // For scripting + "ghcr.io/va-h/devcontainers-features/uv:1": {}, + // For paxtools + "ghcr.io/devcontainers/features/java:1": {} + } +} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..2a65a96 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,87 @@ +name: Test SPRAS + +on: + pull_request: + branches: [main] + push: + branches: [main] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + tests: + name: Test uv environment and run tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install uv for scripting + uses: astral-sh/setup-uv@v6.1.0 + with: + version: "0.7.13" + - name: Run test suite + run: uv run pytest + + pre-commit: + name: Run pre-commit checks + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Run pre-commit checks + uses: pre-commit/action@v3.0.0 + checks: + name: Run workflow + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Install uv for scripting + uses: astral-sh/setup-uv@v6.1.0 + with: + version: "0.7.13" + - name: Setup conda + uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: spras + environment-file: spras/environment.yml + auto-activate-base: false + miniconda-version: 'latest' + # Install spras in the environment using pip + - name: Install spras in conda env + shell: bash --login {0} + run: pip install ./spras + # Log conda environment contents + - name: Log conda environment + shell: bash --login {0} + run: conda list + - name: Fetch Artifact Cache + id: fetch-cache + uses: actions/cache/restore@v4 + with: + path: cache/artifacts + key: cache-artifacts + - name: Process raw data through Snakemake + run: sh run_snakemake.sh + - name: Cache Artifact Cache + id: cache-cache + uses: actions/cache/save@v4 + with: + path: cache/artifacts + key: cache-artifacts + - name: Run Snakemake workflow for EGFR + shell: bash --login {0} + run: snakemake --cores 4 --configfile configs/egfr.yaml --show-failed-logs -s spras/Snakefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..196f12a --- /dev/null +++ b/.gitignore @@ -0,0 +1,169 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# Snakemake +.snakemake + +# Output +/output +/web/output + +# pnpm +.pnpm-store + +# mac +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..11f80da --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spras"] + path = spras + url = https://github.com/Reed-CompBio/spras diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f687b4c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +default_language_version: + # Match this to the version specified in environment.yml + python: python3.11 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 # Use the ref you want to point at + hooks: + # Attempts to load all yaml files to verify syntax. + - id: check-yaml + # Attempts to load all TOML files to verify syntax. + - id: check-toml + # Trims trailing whitespace. + - id: trailing-whitespace + # Preserves Markdown hard linebreaks. + args: [--markdown-linebreak-ext=md] + # Do not trim whitespace from all files, input files may need trailing whitespace for empty values in columns. + types_or: [markdown, python, yaml] + # Skip this Markdown file, which has an example of an input text file within it. + exclude: input/README.md + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.15.4' + hooks: + - id: ruff + - repo: https://github.com/google/yamlfmt + rev: v0.17.0 + hooks: + - id: yamlfmt + - repo: https://github.com/crate-ci/typos + rev: v1.34.0 + hooks: + - id: typos diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6e47f95 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.rulers": [ + 150 // enforced by ruff + ] +} diff --git a/.yamlfmt.yaml b/.yamlfmt.yaml new file mode 100644 index 0000000..9d3236a --- /dev/null +++ b/.yamlfmt.yaml @@ -0,0 +1,2 @@ +formatter: + retain_line_breaks_single: true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c8f92f6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Reed College Computational Biology + +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 cb8e12d..ced9cca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,63 @@ -# spras-benchmarking +# SPRAS benchmarking -See [#64](https://github.com/Reed-CompBio/spras-benchmarking/issues/64). +![example workflow](https://github.com/Reed-CompBio/spras-benchmarking/actions/workflows/publish.yml/badge.svg) + +Benchmarking datasets for the [SPRAS](https://github.com/Reed-CompBio/spras) project. This repository contains different dataset collections to evaluate all algorithms in SPRAS. +The results of every benchmarking run will be deployed on GitHub pages. + +## Setup + +This repository depends on SPRAS. If you want to reproduce the results locally, +you will need to setup SPRAS. SPRAS depends on [Docker](https://www.docker.com/) and [Conda](https://docs.conda.io/projects/conda/en/stable/). If it is hard to install either of these tools, +a [devcontainer](https://containers.dev/) is available for easy setup. + +```sh +conda env create -f spras/environment.yml +conda activate spras +pip install ./spras +``` + +To run the postprocess output scripts, we have a `pyproject.toml` which can be used with your desired python package manager. This separates +the `spras` conda environment from the small scripts we have. (on CI, we use [`uv`](https://docs.astral.sh/uv/).) + +To run the benchmarking pipeline, use (this example is specifically for disease module mining): + +```sh +snakemake --cores 1 --configfile configs/scores.yaml --show-failed-logs -s spras/Snakefile +``` + +To download associated raw files for a dataset and run its pipeline, +run the respective `Snakefile` in the dataset directory using [uv](https://docs.astral.sh/uv/): + +```sh +cd datasets/[dataset] +uv run snakemake --cores 1 +``` + +> [!NOTE] +> Each one of the dataset categories (at the time of writing, scores) are split into different configuration files. +> Run each one as you would want. + +## Organization + +There are six primary folders in this repository: + +``` +. +├── cache +├── configs +├── datasets +├── spras +``` + +`spras` is the cloned submodule of [SPRAS](https://github.com/reed-compbio/spras), +`configs` is the YAML file used to set up workflows in SPRAS, and `datasets` contains the raw and processed data. `cache` is utility for `datasets` which provides a convenient +way to fetch files for further processing. + +The workflow runs as so: + +1. For every dataset, run its inner `Snakefile` with [Snakemake](https://snakemake.readthedocs.io/en/stable/). This is orchestrated +through the top-level [`run_snakemake.sh`](./run_snakemake.sh) shell script. +1. Run each config YAML file in `configs/` with SPRAS. + +For more information on how to add a dataset, see [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..bbf3c6e --- /dev/null +++ b/_typos.toml @@ -0,0 +1,18 @@ +[type.txt] +# Ignore data files +extend-glob = ["*.jsonc", "*.json"] +check-file = false + +[files] +extend-exclude = [ + # PANTHER SPRAS formatting contains an intentional typo + "datasets/synthetic_data/scripts/panther_spras_formatting.py", + # Bad variable names in this file that may be removed later + "datasets/diseases/viz/viz.ipynb" +] + +[default.extend-words] +cna = "cna" +AGS = "AGS" +HEL = "HEL" +PANC = "PANC" \ No newline at end of file diff --git a/cache/.gitignore b/cache/.gitignore new file mode 100644 index 0000000..9554b8c --- /dev/null +++ b/cache/.gitignore @@ -0,0 +1,2 @@ +artifacts +logs diff --git a/cache/README.md b/cache/README.md new file mode 100644 index 0000000..db71dc7 --- /dev/null +++ b/cache/README.md @@ -0,0 +1,114 @@ +# Cache + +Handles artifact fetching and cache. The point of this is to [for the duration that SPRAS is maintained] prevent any kind of +data rot, to ensure that continuous benchmarking is encouraged to use the latest available data. + +During benchmarking runs, data is fetched from all provided URLs in `directory.py`, where we get the cached data, +and compare it to our cached data to check if the data has changed at all. + +All entries are provided with this template (where `CacheItem` is a named tuple): + +```py +"file-name.ext": CacheItem( + name="Short File Description", + cached="https://drive.google.com/uc?id=...", + # These two are optional, but heavily encouraged to be included. + pinned="...", + unpinned="...", +), +``` + +When a file is requested, `cached`, `pinned`, and `unpinned` are all downloaded. `cached` is the link to the underlying file that we store in Drive, +`pinned` is the link to an arbitrary online service containing a versioned static file (we use this to check for uptime and dually to document +where a file was originally retrieved), and `unpinned` is the link to the an arbitrary online service containing an unversioned +(which has the capability of becoming newer) file we use to check for updates. + +We characterize them as follows: +- If the URL linking to `unpinned` or `pinned` does not match `cached`, we warn that the data needs updating. +The data itself will not automatically update. + +Specifically, `unpinned` links to file URLs that constantly update, `pinned` does otherwise, and `cached` links to our +own copy of the data that must match with the `pinned` URL, and is encouraged to match with the `unpinned` URL to prevent our own copy of the data +from drifting with respect to its version. We prefer to have both `pinned` and `unpinned` URLs, but +there are many situations where the `pinned` URL is not available (e.g. the queried service has no versioning), or the `unpinned` URL is not available +(e.g. the queried service only has versioning). + +If `cache` doesn't match `pinned`, this usually indicates that the service is down: we don't have a way to handle temporary outages at the moment, +but permanent outages should remove references to `pinned` entirely, noting that the linked service is down forever for some reason. + +## Google Drive + +We currently use Google Drive to store raw data. The hope is to move to [OSDF](https://osg-htc.org/services/osdf). +We have been running into the occasional ratelimiting issue, which may become more of a problem in the future. + +## Snakemake + +We also provide a `Snakefile`, which contains dataset fetching functions that can be imported in dataset-specific Snakefiles through: + +```py +include: "../../cache/Snakefile" +``` + +This imports a function `produce_fetch_rules`, which takes in a dictionary where the keys are file names, +and the values are either entries in `directory.py`, or `CacheItem`s themselves. For example, + +```py +produce_fetch_rules({ + "raw/9606.protein.links.full.txt": FetchConfig( + ("STRING", "v12", "9606", "9606.protein.links.full.txt.gz"), + post_process=PostProcessAction.UNCOMPRESS_GZ + ), +}) +``` + +would produce a Snakemake rule whose output is `raw/9606.protein.links.full.txt`, and would look under `directory.py` by traversing +the `directory` dictionary, going to `STRING` then `v12` then `9606` then `9606.protein.links.full.txt.gz`, where the `FetchConfig` +asks the inner rule to uncompress the .gz file before saving it under `raw/9606.protein.links.full.txt`. + +> [!NOTE] +> `FetchConfig` is optional, and is only used to specify the `post_process` option. + +Semantically, this is equivalent to: + +```py +produce_fetch_rules({ + "raw/9606.protein.links.full.txt": FetchConfig(CacheItem( + name="STRING 9606 full protein links", + cached="...", + pinned="...", + ), post_process=PostProcessAction.UNCOMPRESS_GZ), +}) +``` + +However, the former option, since it uses items in `directory.py`, saves the file to a cached directory under `cache/artifacts`. +The latter saves the file to a dataset-specific folder for dataset `Snakefile`s: that is, if you have a file +that's used across multiple datasets, add it to `directory.py`. Otherwise, if you have a file specifically used for a dataset, keep it under that +respective dataset. + +```py +FetchConfig(("STRING", "v12", "9606", "9606.protein.links.full.txt.gz"), post_process=PostProcessAction.UNCOMPRESS_GZ) +``` + +## Implementation details + +### `.metadata` + +All cached files come with an associated `.metadata`: usually, this would be controlled with Snakemake, but since this system lives +outside of the purview of `Snakemake`, we instead track file data with an associated `.metadata` file, which preserves information +about where the file came from, and when it was created, to re-fetch files if any of that associated data changes. +This is controlled under `link.py`. + +### Loggers + +Later on ([#72](https://github.com/Reed-CompBio/spras-benchmarking/issues/72)), our use of `loguru` +will be logged to let maintainers know when data sources are outdated. + +### Layout + +This folder has: +- `Snakefile` which only contains a function used for producing fetching rules. +- `directory.py`, where named `CacheItem`s are stored, as well as the code that defines +the schema (including `CacheItem`) for the rest of this directory. +- `cli.py`, a debugging utility for manually fetching specific data from `directory.py`. +- `util.py`, an internal file for use by the other files above. +- `link.py`, which acts as an intermediary between `Snakefile` and `directory.py`, providing utilities for handling file metadata. diff --git a/cache/Snakefile b/cache/Snakefile new file mode 100644 index 0000000..5451fdf --- /dev/null +++ b/cache/Snakefile @@ -0,0 +1,36 @@ +""" +This Snakefile is not meant to be ran - rather, it is meant to be imported by +other Snakefiles. This can't be a Python script since it uses rule generation in +`produce_fetch_rules`. +""" + +from cache.link import FetchConfig, link, stringify_config +from cache.directory import CacheItem # dually to expose to Snakefiles that import this Snakefile. +from cache.util import PostProcessAction +import urllib.parse +from typing import Union +from pathlib import Path + +def produce_fetch_rules(input_dict: dict[str, Union[FetchConfig, list[str]]]): + """ + Produces fetch rules based on a dictionary mapping + output files to their directory.py-based directive. + """ + # Map inputs to be wrapped with FetchConfig if tuple[str, ...] or CacheItem + input_dict = {k: FetchConfig(v) if isinstance(v, tuple) or isinstance(v, CacheItem) else v for k, v in input_dict.items()} + + directives = list(input_dict.values()) + assert len(directives) == len(set(directives)), "Directives aren't unique! This means several files are unnecessarily pointing to the same item." + + for output_file, config in input_dict.items(): + # Since placeholders are evaluated when the job is actually ran, + # we pass data using params and output. + rule: + name: + f"fetch_{stringify_config(config.directive)}_to_{urllib.parse.quote_plus(output_file)}" + output: file=output_file + params: + config=config + run: + Path(output.file).parent.mkdir(exist_ok=True) + link(Path(output.file), params.config) diff --git a/cache/__init__.py b/cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cache/biomart/README.md b/cache/biomart/README.md new file mode 100644 index 0000000..8a32031 --- /dev/null +++ b/cache/biomart/README.md @@ -0,0 +1,6 @@ +# BioMart XML Queries + +Directory for storing XML queries generated from [the BioMart interface](https://www.ensembl.org/info/data/biomart/index.html), +which provides universal mappings regardng different biological datasets. See the martview: https://www.ensembl.org/biomart/martview. + +See `../directory.py` for usage. diff --git a/cache/biomart/ensg-ensp.xml b/cache/biomart/ensg-ensp.xml new file mode 100644 index 0000000..b805c2e --- /dev/null +++ b/cache/biomart/ensg-ensp.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/cache/cli.py b/cache/cli.py new file mode 100644 index 0000000..2d133fd --- /dev/null +++ b/cache/cli.py @@ -0,0 +1,30 @@ +""" +Downloads the online variants of cache items. + +This can be expanded in the future, so only depend on this file as a debugging utility. + +For example, `python cache/cli.py a/b.c b.c` would download the file under `a`, `b.c` in `directory` +to the file `b.c`: this is especially useful if headers are provided in `directory.py` +""" + +import argparse +from cache.directory import get_cache_item + + +def parse_args(): + parser = argparse.ArgumentParser(prog="Cache", description="CLI utility for directory.py") + parser.add_argument("path") + parser.add_argument("output") + + return parser.parse_args() + + +def main(): + args = parse_args() + cache_item = get_cache_item(args.path.split("/")) + + cache_item.download(args.output) + + +if __name__ == "__main__": + main() diff --git a/cache/directory.py b/cache/directory.py new file mode 100644 index 0000000..505e72a --- /dev/null +++ b/cache/directory.py @@ -0,0 +1,335 @@ +from dataclasses import dataclass +from typing import Union +from os import PathLike +from tempfile import NamedTemporaryFile +from typing import Optional, Mapping +import filecmp +from pathlib import Path +import warnings +from frozendict import frozendict +import requests +import shutil +import urllib.parse + +import gdown +from loguru import logger + +dir_path = Path(__file__).parent.resolve() + +# Our cache emits warnings for files with unpinned versions that don't match the cache +# using loguru, and warnings are added to a local `logs` folder. +(dir_path / "logs").mkdir(exist_ok=True) +logger.add(dir_path / "logs" / "cache.log", level="WARNING") + + +@dataclass +class Service: + url: str + headers: Optional[Mapping[str, str]] = None + + def download(self, output: str | PathLike) -> requests.Response: + """ + Downloads a URL, returning the response (to be used with `with`) and modifying the output path. + """ + # As per https://stackoverflow.com/a/39217788/7589775 to enable download streaming. + with requests.get(self.url, stream=True, headers=self.headers, allow_redirects=True) as response: + response.raw.decode_content = True + with open(output, "wb") as f: + shutil.copyfileobj(response.raw, f) + return response + + # NOTE: this is slightly yucky code deduplication. The only intended values of `downloaded_file_type` are `pinned` and `unpinned`. + def download_against_cache(self, cache: Path, downloaded_file_type: str): + """ + Downloads `self` Service and checks it against the provided `cache` at path. In logs, + the file will be referred to as `downloaded_file_type`. + """ + logger.info(f"Downloading {downloaded_file_type} file {self.url} to check against with artifact at {cache}...") + downloaded_file_path = Path(NamedTemporaryFile(delete=False).name) + + self.download(downloaded_file_path) + logger.info(f"Checking that the {downloaded_file_type} artifact {downloaded_file_path} matches with cached artifact at {cache}...") + + if not filecmp.cmp(cache, downloaded_file_path): + # This entire if-branch is debug shenanigans: we want to be able to easily compare our current cached file to the online file, + # especially since some `Service`s have special errors that can make the request hard to compare in the browser. + + debug_file_path = Path(NamedTemporaryFile(prefix="spras-benchmarking-debug-artifact", delete=False).name) + # We use shutil over Path#rename since temporary directories can be mounted to a different file system. + shutil.copy(cache, debug_file_path) + logger.warning( + f"The {downloaded_file_type} file {downloaded_file_path} and " + + f"cached file originally at {cache} do not match! " + + f"Compare the pinned {downloaded_file_path} and the cached {debug_file_path}. " + + "If this file updated, please update the underlying `cache` file to match." + ) + else: + # Since we don't clean up pinned_file_path for the above branch's debugging, + # we need to clean it up here. + downloaded_file_path.unlink() + + @staticmethod + def coerce(obj: "Service | str") -> "Service": + # TODO: This could also be replaced by coercing str to Service in CacheItem via pydantic. + if isinstance(obj, str): + return Service(url=obj) + return obj + + +def fetch_biomart_service(xml: str, archived: bool = False) -> Service: + """ + Access BioMart data through the BioMart REST API: + https://useast.ensembl.org/info/data/biomart/biomart_restful.html#biomartxml + + We also provide links to the Ensembl archives for pinned files. + """ + ROOT = "http://www.ensembl.org/biomart/martservice?query=" + ROOT_ARCHIVED = "http://sep2025.archive.ensembl.org/biomart/martservice?query=" + return Service((ROOT_ARCHIVED if archived else ROOT) + urllib.parse.quote_plus(xml)) + + +@dataclass(frozen=True) +class CacheItem: + """ + Class for differentriating between different ways of fetching data. + As mentioned in the ./README.md, `cached` is always needed, and we differentiate between service outage (`pinned`) + and data needing updates (`unpinned`). There is no need to specify both keys at once, but the choice does matter + for how errors are presented during benchmarking runs. + """ + + name: str + """The display name of the artifact, used for human-readable logs.""" + + cached: str + """ + The URL of the cached file, which is currently a Google Drive URL. + """ + + pinned: Optional[Service | str] = None + """ + The Service (URL + headers) of the file, which is the 'pinned' file. + By a pinned file, we say that the file has a dedicated version, and should not change. + If this is None, we go for the `unpinned` file or `cached` if `unpinned` is None. + + If pinned` doesn't match `cached`, we emit a warning. + """ + + unpinned: Optional[Service | str] = None + """ + Analogously to `pinned`, this is a Service (URL + headers) which is 'unpinned,' + or lacks a dedicated version. When `pinned` matches `cached` but `unpinned` doesn't match `pinned`, + we say that the file has a new version but won't be automatically updated to the new version. + + If unpinned` doesn't match `cached`, we emit a warning. + """ + + def __post_init__(self): + # Google Drive validation. TODO: remove if move to OSDF. + if "uc?id=" not in self.cached or "/view?usp=sharing" in self.cached: + raise RuntimeError( + "Make sure your Google Drive URLs are in https://drive.google.com/uc?id=... format " + + "with no /view?usp=sharing at the end. See CONTRIBUTING.md for more info." + ) + + @classmethod + @warnings.deprecated("Pending for removal after the CONTRIBUTING guide is updated.") + def cache_only(cls, name: str, cached: str) -> "CacheItem": + """Wrapper method to explicitly declare a CacheItem as cached only.""" + return cls(name=name, cached=cached) + + def download(self, output: str | PathLike): + """ + Downloads this `CacheItem` to the desired `output`, + comparing the `cached` file to the `pinned` and `unpinned` files, + warning when `cached` doesn't match `unpinned`, and erroring when + `cached` doesn't match `pinned`. + + The file from `cache` is the file that gets downloaded to `output`. + """ + logger.info(f"Fetching {self.name}...") + + logger.info(f"Downloading cache {self.cached} to {output}...") + gdown.download(self.cached, str(output)) # gdown doesn't have a type signature, but it expects a string + + if self.pinned is not None: + Service.coerce(self.pinned).download_against_cache(cache=Path(output), downloaded_file_type="pinned") + if self.unpinned is not None: + Service.coerce(self.unpinned).download_against_cache(cache=Path(output), downloaded_file_type="unpinned") + + +CacheDirectory = frozendict[str, Union[CacheItem, "CacheDirectory", str]] + +# The directory list containing versioned and unversioned files. +directory: CacheDirectory = frozendict({ + # STRINGDB: https://string-db.org/ + # You can see more information about these files at https://string-db.org/cgi/download. + "STRING": { + "v12": { + "9606": { + "9606.protein.links.full.txt.gz": CacheItem( + name="STRING 9606 full protein links v12", + cached="https://drive.google.com/uc?id=13tE_-A6g7McZs_lZGz9As7iE-5cBFvqE", + pinned="http://stringdb-downloads.org/download/protein.links.full.v12.0/9606.protein.links.full.v12.0.txt.gz", + ), + "9606.protein.aliases.txt.gz": CacheItem( + name="STRING 9606 protein aliases v12", + cached="https://drive.google.com/uc?id=1IWrQeTVCcw1A-jDk-4YiReWLnwP0S9bY", + pinned="https://stringdb-downloads.org/download/protein.aliases.v12.0/9606.protein.aliases.v12.0.txt.gz", + ), + "9606.protein.physical.links.v12.0.txt.gz": CacheItem( + name="STRING 9606 physical subnetwork, scored links between proteins v12", + cached="https://drive.google.com/uc?id=1qCo9a04IPRCAP0B_jDuOWAWAL7bE5Oz9", + pinned="https://stringdb-downloads.org/download/protein.physical.links.v12.0/9606.protein.physical.links.v12.0.txt.gz" + ) + } + }, + }, + # https://www.uniprot.org/ + "UniProt": { + # We use FTP when possible, but we delegate to the UniProt REST API in cases that would save significant bandwidth. + # See https://ftp.uniprot.org/pub/databases/uniprot/current_release/README for the FTP README. + # 9606 is human. + # All of the following data lives under the 2026-03-25 folder, or its retrieval date. + "9606": { + # We prefer manually curated, or SwissProt, genes. + # This URL selects these genes using the REST API. + # UniProt REST doesn't seem to have any way to version it, so we only provide the `unpinned` URL. + "SwissProt_9606.tsv": CacheItem( + name="UniProt 9606 SwissProt genes", + cached="https://drive.google.com/uc?id=1qa5PvyYuc7Sg4NNqlId6MXvKokTls3ND", + unpinned="https://rest.uniprot.org/uniprotkb/stream?fields=accession%2Cid%2Cprotein_name%2Cgene_names&format=tsv&query=%28*%29+AND+%28reviewed%3Atrue%29+AND+%28model_organism%3A9606%29", + ), + # idmapping FTP files. See the associated README: + # https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/idmapping/README + # We use these files as our primary source of identifier mapping. + # Unfortunately, there are no accompanying `pinned` URLs for these, as the previous releases + # contain files with data magnitudes higher than anything we process. + "HUMAN_9606_idmapping_selected.tab.gz": CacheItem( + name="UniProt 9606 ID external database mapping", + cached="https://drive.google.com/uc?id=1sKYVSQgTne3fg0pauFno0FjVYElG4VJy", + unpinned="https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/idmapping/by_organism/HUMAN_9606_idmapping_selected.tab.gz", + ), + "HUMAN_9606_idmapping.dat.gz": CacheItem( + name="UniProt 9606 internal id mapping", + cached="https://drive.google.com/uc?id=1QfjjVn36PzJx9ZUxtNSCOOwoZbJpLIiZ", + unpinned="https://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/idmapping/by_organism/HUMAN_9606_idmapping.dat.gz", + ), + } + }, + + # https://www.ensembl.org/info/data/biomart/index.html + "BioMart": { + # ENSG to ENSP mapping + # we generally prefer UniProt mappings over this, since we usually work over UniProt-compatible + # ENSG/ENSP identifiers, but sometimes identifiers live outside of this space. + "ensg-ensp.tsv": CacheItem( + name="BioMart ENSG <-> ENSP mapping", + cached="https://drive.google.com/uc?id=1-gPrDoluXIGydzWKjWEnW-nWhYu3YkHL", + unpinned=fetch_biomart_service((dir_path / "biomart" / "ensg-ensp.xml").read_text()), + pinned=fetch_biomart_service((dir_path / "biomart" / "ensg-ensp.xml").read_text(), archived=True), + ) + }, + # https://www.pathwaycommons.org/ + "PathwayCommons": { + "pathways.txt.gz": CacheItem( + name="PathwayCommons Pathway Identifiers", + cached="https://drive.google.com/uc?id=1SMwuuohuZuNFnTev4zRNJrBnBsLlCHcK", + pinned="https://download.baderlab.org/PathwayCommons/PC2/v14/pathways.txt.gz", + # While we would like an unpinned version, the PathwayCommons API is deeply unreliable, + # nor does it provide a full text file of pathways, + # and the FTP server (https://download.baderlab.org/PathwayCommons/PC2/) + # does not provide unversioned files. + ), + }, + + "DepMap_25Q3": { + "OmicsProfiles_25Q3.csv": CacheItem( + name="DepMap omics metadata", + cached="https://drive.google.com/uc?id=10eJ6ZcDlxEBsfHsvkmrgtTEd__71fC_7", + # pinned="https://depmap.org/portal/download/api/download?file_name=downloads-by-canonical-id%2F2025-05-01-master-mapping-table-28c2.12%2Fpublic_release_date.2025-05-01.master_mapping_table.csv&dl_name=OmicsProfiles.csv&bucket=depmap-external-downloads", + ), + + "CRISPRGeneDependency_25Q3.csv": CacheItem( + name="DepMap crispr gene dependency probability estimates", + cached="https://drive.google.com/uc?id=18hGSWlWEo_R6FmYziZKEISYCbDqrNty_", + # pinned="https://depmap.org/portal/download/api/download?file_name=downloads-by-canonical-id%2F25q2-public-557c.3%2FCRISPRGeneDependency.csv&dl_name=CRISPRGeneDependency.csv&bucket=depmap-external-downloads", + ), + + "OmicsSomaticMutationsMatrixDamaging_25Q3.csv": CacheItem( + name="DepMap genotyped matrix", + cached="https://drive.google.com/uc?id=1M-ybvvKvGbdRhPLGjtBzKJviVecVXbDi", + # pinned="https://depmap.org/portal/download/api/download?file_name=downloads-by-canonical-id%2Fpublic-25q2-c5ef.87%2FOmicsSomaticMutationsMatrixDamaging_25Q3.csv&dl_name=OmicsSomaticMutationsMatrixDamaging_25Q3.csv&bucket=depmap-external-downloads", + ), + + "Model_25Q3.csv": CacheItem( + name="DepMap Metadata describing all cancer models/cell lines which are referenced by a dataset contained within the DepMap portal.", + cached="https://drive.google.com/uc?id=1-26H3bvAAEt7UjlxpiK1cxORiAGJnxU9" + ), + }, + + # TODO: update the ccle data once the new pr gets merged: https://github.com/cBioPortal/datahub/pull/2283 + "cbioportal": { + "data_cna_cbioportal_ccle2019.txt": CacheItem( + name="Copy number alterations from CCLE", + cached="https://drive.google.com/uc?id=1C-OQu80Ptfy0-aBWg6nlULcQQvfzBBPp" + ), + }, + + "CCLE_2019": { + "Cell_lines_annotations_20181226_ccle2019.txt": CacheItem( + name = "Cell line annotations", + cached="https://drive.google.com/uc?id=1MDe_MizqSfaH58UMrwzhorXStMNCNNBy" + ) + }, + + "estimateNCA": { + "consensus_tfa_march_6.tsv": CacheItem( + name = "", + cached="https://drive.google.com/uc?id=1haTqjyqkWoYTLw_6tubc0rJUiJIszC6v" + ), + + "tfs_beyond_2sd_per_cell_line.csv": CacheItem( + name="", + cached="https://drive.google.com/uc?id=1vl6t_8bXfbyCYzaw9u2HpU430O_IMHWm" + ) + }, + + "cancer_cellline_misc": { + "shared_cell_lines_may_12.txt": CacheItem( + name="", + cached="https://drive.google.com/uc?id=1NDiTiOgWpGXn-pWB0ZWlizYAo9C_SAGH" + ) + + }, + +}) + + +def get_cache_item(path: tuple[str, ...], custom_directory: Optional[CacheDirectory] = None) -> CacheItem: + """ + Takes a path and gets the underlying cache item. + If `custom_directory` is `None`, the top-level `directory` is used instead. + """ + assert len(path) != 0 + + current_item: CacheDirectory | CacheItem = directory if custom_directory is None else custom_directory + for entry in path: + if isinstance(current_item, CacheItem): + raise ValueError(f"Path {path} leads to a cache item too early!") + + # We do this since inlining current_item[entry] scares the type-checker into believing + # __getitem__ isn't pure with respect to the notably frozen dictionary, and not because we use + # next_item later. + next_item = current_item[entry] + if isinstance(next_item, str): + # This points somewhere else: we use this for explicitly versioning files. + entry = next_item + next_item = current_item[entry] + assert not isinstance(next_item, str), "We don't support nested aliasing! (i.e. no unversioned1 -> unversioned2 -> v1 -> `CacheItem`)" + + current_item = next_item + + if not isinstance(current_item, CacheItem): + raise ValueError(f"Path {path} doesn't lead to a cache item. It instead leads to '{current_item}'") + + return current_item diff --git a/cache/link.py b/cache/link.py new file mode 100644 index 0000000..9732a40 --- /dev/null +++ b/cache/link.py @@ -0,0 +1,138 @@ +""" +This is how spras-benchmarking handles artifact downloading and caching. +`cache.link` should be used specifically inside `Snakefile` +""" + +from dataclasses import dataclass +from pathlib import Path +import pickle +from urllib.parse import quote_plus +from typing import Optional, Union + +from cache.util import PostProcessAction +from cache.directory import CacheItem, get_cache_item + + +__all__ = ["FetchConfig", "link"] + +dir_path = Path(__file__).parent.resolve() +artifacts_dir = dir_path / "artifacts" + + +@dataclass(frozen=True) +class FetchConfig: + """ + What `directive` (a CacheItem or a location in `./directory.py`) + should be fetched, and how it should be fetched. + """ + + directive: Union[CacheItem, tuple[str, ...]] + post_process: Optional[PostProcessAction] = None + + +def stringify_tuple_directive(directive: tuple[str, ...]) -> str: + return quote_plus("/".join(directive)) + + +def stringify_config(directive: Union[CacheItem, tuple[str, ...]]) -> str: + """ + Directives aren't friendly for Snakemake rules: we use urllib to make a URL-safe string, as Snakemake accepts + such strings as rule names. They aren't the most readable, but they suffice for logging purposes. + """ + return quote_plus(directive.name if isinstance(directive, CacheItem) else "/".join(directive)) + + +def add_suffix(path: Path, suffix: str): + """`file.suffix` -> `file.suffix.suffix2`.""" + return path.with_suffix(path.suffix + suffix) + + +def metadata_has_expired(cache_item: CacheItem, output: Path) -> bool: + """ + Check if the artifact metadata (the serialized `cache_item`) associated with a `cache_item` has expired (i.e. changed or deleted). + Avoids re-downloading the artifact if nothing has changed. + """ + + metadata_file = add_suffix(output, ".metadata") + + # metadata never existed: we need to retrieve the new file + if not metadata_file.exists(): + with open(metadata_file, "wb") as f: + pickle.dump(cache_item, f) + return True + + old_cache_item = None + with open(metadata_file, "rb") as f: + old_cache_item = pickle.load(f) + + # metadata does not match the `cache_item`: re-retrieve the item + if old_cache_item != cache_item: + with open(metadata_file, "wb") as f: + pickle.dump(cache_item, f) + return True + + # metadata hasn't changed and already existed: this hasn't expired + return False + + +def link_with_cache_item(output: Path, cache_item: CacheItem, post_process: Optional[PostProcessAction] = None): + """ + Intermediary function for `link`. + This does almost all of what `link` is characterized to do in its documentation, + except for doing symlinking. + """ + # If `uncompress` is `True`, we make + # `output` our 'compressed output.' + uncompressed_output = output + if post_process is not None: + output = add_suffix(output, ".unprocesed") + + # Re-download if the file doesn't exist or the directive has expired. + # Note that we check for expiration first to trigger metadata creation. + if metadata_has_expired(cache_item, output) or not output.exists(): + output.unlink(missing_ok=True) + cache_item.download(output) + + if post_process is not None: + processed_artifact_path = add_suffix(output, ".processed") + processed_artifact_path.unlink(missing_ok=True) + post_process.run_action(output, uncompressed_output) + + +def link(output: str, config: FetchConfig): + """ + Links output files from the provided `config` to the provided `output`. + + @param output: The output file to write to. + @param config: The config to fetch from. + + For example, + + ```python + link("raw/ensg-ensp.tsv", FetchConfig(("BioMart", "ensg-ensp.tsv"))) + ``` + + Will download and check BioMart's cache for ENSG-ENSP mapping, then: + - If `config.directive` is a `CacheItem`, we write the file directly to `output`. + - Otherwise, we symlink the cached output (lying in `artifacts_dir`) with the desired `output` + to avoid unnecessary file duplication. + + In the above example's case, since the configuration points to an entry in `directory.py`, + we would do the latter symlinking option, avoiding querying the BioMart API in case this data + is requested again. + + This function wraps around `link_with_cache_item` and handles symlinking + depending on the type of config.directive. + """ + # TODO: there is most likely a nicer way to design this. + + if isinstance(config.directive, CacheItem): + link_with_cache_item(Path(output), config.directive, config.post_process) + else: + artifacts_dir.mkdir(exist_ok=True) + artifact_name = stringify_tuple_directive(config.directive) + artifact_output = artifacts_dir / artifact_name + + link_with_cache_item(artifact_output, get_cache_item(config.directive), config.post_process) + + Path(output).symlink_to(artifact_output) diff --git a/cache/util.py b/cache/util.py new file mode 100644 index 0000000..d3b67e6 --- /dev/null +++ b/cache/util.py @@ -0,0 +1,38 @@ +from enum import Enum +from pathlib import Path +import gzip +import shutil +import zipfile + +__all__ = ["PostProcessAction"] + + +def uncompress_gz(source: Path, target: Path): + """Uncompresses a .gz file""" + # https://stackoverflow.com/a/44712152/7589775 + with gzip.open(source, "rb") as f_compressed: + with open(target, "wb") as f_uncompressed: + shutil.copyfileobj(f_compressed, f_uncompressed) + + +def uncompress_zip(source: Path, target: Path): + """Uncompress a .zip file""" + # https://stackoverflow.com/a/3451150/7589775 + with zipfile.ZipFile(source, "r") as zip_ref: + zip_ref.extractall(target) + + +class PostProcessAction(Enum): + UNCOMPRESS_GZ = "uncompress_gz" + """Uncompresses a .gz file. The `out_path` here is a file.""" + UNCOMPRESS_ZIP = "uncompress_zip" + """Uncompresses a .zip file. The `out_path` here is a directory.""" + + # NOTE: this can be generalized to multiple outputs and multiple steps, but I'm explicitly avoiding generalization + # here, as else you run into a general pipeline design pattern when Snakemake can just handle this for us. + def run_action(self, in_path: Path, out_path: Path): + match self: + case self.UNCOMPRESS_GZ: + uncompress_gz(in_path, out_path) + case self.UNCOMPRESS_ZIP: + uncompress_zip(in_path, out_path) diff --git a/configs/depmap.yaml b/configs/depmap.yaml new file mode 100644 index 0000000..b2cd7f6 --- /dev/null +++ b/configs/depmap.yaml @@ -0,0 +1,46 @@ +hash_length: 7 + +containers: + registry: + base_url: docker.io + owner: reedcompbio + framework: docker + unpack_singularity: false + +reconstruction_settings: + locations: + reconstruction_dir: "output" + +analysis: + summary: + include: false + cytoscape: + include: false + ml: + # TODO: we either need at least one non-empty pathway in all algorithm runs, + # or we need to get rid of validate_df hard-erroring. The latter seems better. + include: false + aggregate_per_algorithm: true + evaluation: + include: false + +# Custom settings +algorithms: + - name: "omicsintegrator1" + include: true + runs: + run1: + b: 2 + w: .5 + d: 10 + mu: 2 + # TODO: egfr prefers dummy_mode: ["file"] since we manually specify EGF_HUMAN as one. + - name: "omicsintegrator2" + include: true + runs: + run1: + b: 4 + g: 0 +# TODO add all the rest of the algorithms with default parameters for now + +# add every cell line to the yaml file diff --git a/configs/egfr.yaml b/configs/egfr.yaml new file mode 100644 index 0000000..5614c5b --- /dev/null +++ b/configs/egfr.yaml @@ -0,0 +1,55 @@ +hash_length: 7 + +containers: + registry: + base_url: docker.io + owner: reedcompbio + framework: docker + unpack_singularity: false + +reconstruction_settings: + locations: + reconstruction_dir: "output" + +analysis: + summary: + include: false + cytoscape: + include: false + ml: + # TODO: we either need at least one non-empty pathway in all algorithm runs, + # or we need to get rid of validate_df hard-erroring. The latter seems better. + include: false + aggregate_per_algorithm: true + evaluation: + include: false + +# Custom settings +algorithms: + - name: "omicsintegrator1" + include: true + runs: + run1: + b: 2 + w: .5 + d: 10 + mu: 2 + # TODO: egfr prefers dummy_mode: ["file"] since we manually specify EGF_HUMAN as one. + - name: "omicsintegrator2" + include: true + runs: + run1: + b: 4 + g: 0 + +datasets: + - label: scoresegfr_string + data_dir: datasets/egfr + edge_files: ["processed/ensp/interactome.tsv"] + node_files: ["processed/ensp/input-nodes.txt"] + other_files: [] +gold_standards: + - label: gs_egfr_string + node_files: ["processed/ensp/gold-standard-nodes.txt"] + data_dir: datasets/egfr + dataset_labels: ["scoresegfr_string"] diff --git a/datasets/README.md b/datasets/README.md new file mode 100644 index 0000000..cdbec10 --- /dev/null +++ b/datasets/README.md @@ -0,0 +1,21 @@ +# datasets + +Datasets contains both the raw data (straight from the study/database), as well as Python scripts and an associated Snakemake file +which take all of the raw data and produce SPRAS-compatible data. + +## Prior work + +Many of the datasets here have been stripped of their extra post-analysis. Here, we provide commit links to the original work. + +- [`hiv`](https://github.com/Reed-CompBio/spras-benchmarking/blob/0293ae4dc0be59502fac06b42cfd9796a4b4413e/hiv-benchmarking) +- [`diseases`](https://github.com/Reed-CompBio/spras-benchmarking/tree/3c0155567dbc43278531b91f9173f6d4f4486dd8/datasets/diseases) +- [`depmap`](https://github.com/Reed-CompBio/spras-benchmarking/tree/b332c0ab53868f111cb89cd4e9f485e8c19aa9e3/datasets/depmap) +- [`yeast_osmotic_stress`](https://github.com/Reed-CompBio/spras-benchmarking/tree/8f69dcdf4a52607347fe3a962b753df396e44cda/yeast_osmotic_stress) +- [`responsenet_muscle_skeletal`](https://github.com/Reed-CompBio/spras-benchmarking/tree/33eacc85cc0749c93f497098c5be00051d3b013a) +- [`egfr`](https://github.com/Reed-CompBio/spras-benchmarking/tree/a80683880a3dc8275ebb6efec16758426bb0f01e) +- [`synthetic_data`](https://github.com/Reed-CompBio/spras-benchmarking/tree/d882814386d17d819733c268d8731d51c8bd0cea) + +## `explore` folders + +To motivate certain decisions made in-code, such as `synthetic_data`'s PANTHER pathway choices, we provide scripts that use live data +to assist in data curation. These folders can also contain exploratory CLIs for motivating e.g. magic constants. diff --git a/datasets/__init__.py b/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/datasets/depmap/.gitignore b/datasets/depmap/.gitignore new file mode 100644 index 0000000..53e1047 --- /dev/null +++ b/datasets/depmap/.gitignore @@ -0,0 +1,4 @@ +raw +testing +processed +preprocessed \ No newline at end of file diff --git a/datasets/depmap/README.md b/datasets/depmap/README.md new file mode 100644 index 0000000..e2a966c --- /dev/null +++ b/datasets/depmap/README.md @@ -0,0 +1,83 @@ +# UPDATE THE WHOLE README + +# Cancer Dependency Map Dataset + +This folder contains the processed data and the scripts for data analysis and preparation on datasets from The [Cancer Dependency Map](https://depmap.org/portal/), an initiative led by the Broad Institute to provide large-scale omics data in identifying cancer dependencies/vulnerabilities. + +You can read more about DepMap and the projects included here: https://www.broadinstitute.org/cancer/cancer-dependency-map + +## Raw Data +You can visit the DepMap all data downloads portal at: https://depmap.org/portal/data_page/?tab=allData +Download the following datasets under the primary files section of DepMap and move them to a directory named `raw` that you create. The dataset descriptions from the website are also included: + +Currently used files: + +- `OmicsProfiles.csv`: Omics metadata and ID mapping information for files indexed by Profile ID. This dataset is used for mapping cell line names to DepMap model IDs as a basis for data processing. (file URL: https://depmap.org/portal/data_page/?tab=allData&releasename=DepMap%20Public%2025Q3&filename=OmicsProfiles.csv) +# todo: I am using Model.csv and annotations from the ccle data not omics profiles rn + + +- `CRISPRGeneDependency.csv`: Gene dependency probability estimates for all models in the integrated gene effect. This dataset is used to identify gold standard genes in each cell line, a dependency probability cutoff of 0.5 is currently used to get the genes with considerable impact on the cell line. (file URL: https://depmap.org/portal/data_page/?tab=allData&releasename=DepMap%20Public%2025Q3&filename=CRISPRGeneDependency.csv) +- `OmicsSomaticMutationsMatrixDamaging.csv`: Genotyped matrix determining for each cell line whether each gene has at least one damaging mutation. A variant is considered a damaging mutation if LikelyLoF == True. (0 == no mutation; If there is one or more damaging mutations in the same gene for the same cell line, the allele frequencies are summed, and if the sum is greater than 0.95, a value of 2 is assigned and if not, a value of 1 is assigned.). This dataset is used to prepare the sources and prizes for the input node file. (file URL: https://depmap.org/portal/data_page/?tab=allData&releasename=DepMap%20Public%2025Q3&filename=OmicsSomaticMutationsMatrixDamaging.csv) +- Add the cn data +- add the tfs + +## Scripts +Currently contains: +- `local_cell_line_preprocessing.ipynb`: Jupyter notebook for exploratory data analysis and initial pipeline development. Includes CRISPR dependency analysis with multiple thresholds, visualization of gene dependency distributions, UniProt ID mapping workflow (both gene symbols and gene numbers approaches currently), and step-by-step generation of prize input files and gold standard files for individual cell lines. +- `cell_line_processing.py`: General cell line processing pipeline for generating prize input files and gold standard files converted into Python scripts. Should be reproducible for any cell line name, could be further organized and refined. +- `uniprot_mapping.py`: Gene symbol extraction script for UniProt ID mapping preparation. Parses gene symbols from any DepMap dataset column headers (e.g., "GENE_NAME (12345)" format) and saves them as CSV files ready for input to the UniProt web service. Currently used to extract gene symbols from `OmicsSomaticMutationsMatrixDamaging.csv`, but should be compatible with any omics dataset. + + +Files used for preparing required files: +- `OmicsProfiles.csv` used for mapping cell line names to DepMap model IDs. +- `OmicsSomaticMutationsMatrixDamaging.csv` used for preparing prize input file. +- `CRISPRGeneDependency.csv` used for preparing gold standard output. +- `OmicsExpressionProteinCodingGenesTPMLogp1.csv`: Model-level TPMs derived from Salmon v1.10.0 (Patro et al 2017) Rows: Model IDs Columns: Gene names. +- `OmicsCNGeneWGS.csv`: Gene-level copy number data inferred from WGS data only. Additional copy number datasets are available for download as part of the full DepMap Data Release. + +## Processed Data +Files used for UniProt ID mapping: +- `DamagingMutationsGeneSymbols.csv`: Gene symbols and Gene IDs parsed from gene columns in `OmicsSomaticMutationsMatrixDamaging.csv` on the date described +- `DamagingMutations_idMapping.tsv`: Gene symbols from `DamagingMutationsGeneSymbols_20250718.csv` mapped to UniProt SwissProt IDs, using Gene ID data +to provide more accurate mappings when possible, since gene symbol -> UniProt mappings are not one-to-one mapping. (TODO: some Gene ID -> UniProt +mappings are also not one-to-one: the accuracy could be improved by identifying the gene via the mutations present in the associated matrix.) + +Started processing with the FADU cell line: +- Input prize file prepared from the damaging mutations dataset +- Gold standard file prepared from the CRISPR gene dependency dataset + +## Config +Example Config file used to get preliminary results on OmicsIntegrator1 and 2 following the EGFR dataset example. Will test out more parameters and update. + +## Data types +The variant (i.e. mutation) scores from DepMap are originally described as +> A variant is considered a damaging mutation if LikelyLoF == True. (0 == no mutation; If there is one or more damaging mutations in the same gene for the same cell line, the allele frequencies are summed, and if the sum is greater than 0.95, a value of 2 is assigned and if not, a value of 1 is assigned.) + +Our interpretation of this is +- 0: their variant scoring algorithm assesses this variant to be not functional, that is, not damaging, so we ignore it when assigning prizes +- 1: their scoring algorithm assesses the variant to be functional and it occurs less than all the time (AF < 0.95), which can happen in many ways in cancer (there are multiple clones in the tumor, there is a single clone but one copy of the gene has the mutation and one doesn't); there could also be multiple different mutations in the same gene but they all have frequencies that still sum to less than 0.95 +- 2: the gene is predicted to be severely functionally impacted because there are multiple different mutations each assessed to be functional, and cumulatively they have allele frequency approaching 1 or more + +We retain the difference in score of 1 or 2 because genes with a score of 2 are more important than genes with a score of 1, per the DepMap scoring scheme. +The DepMap [pipeline documentation](https://storage.googleapis.com/shared-portal-files/Tools/25Q3_Mutation_Pipeline_Documentation.pdf) provides more information about how they derive the original scores, such as VEP for variant effect prediction and gnomAD for allele frequencies. + + +## Release Citation +For DepMap Release data, including CRISPR Screens, PRISM Drug Screens, Copy Number, Mutation, Expression, and Fusions: +DepMap, Broad (2025). DepMap Public 25Q2. Dataset. depmap.org + + +# ID mapping + +All depmap and ccle/cbioportal data uses hgnc gene naming for the proteins +STRING physical links uses ensp +Alias gives a mapping from hgnc_symbol to ensp +Turning everything into ENSP + + +# chosen cell lines + +1) for the raw data, chose all the cell lines that were matching +- look at the actual code and define more for the sharedlines + +2) then once mapping and trimming was done, removing cell lines that are missing targets or sources (need at least one source and one target) \ No newline at end of file diff --git a/datasets/depmap/Snakefile b/datasets/depmap/Snakefile new file mode 100644 index 0000000..4891cf2 --- /dev/null +++ b/datasets/depmap/Snakefile @@ -0,0 +1,715 @@ +include: "../../cache/Snakefile" + +# 627 final cell lines +# should match what is in final_cell_lines.csv +CELL_LINES = [ + "A172_CENTRAL_NERVOUS_SYSTEM", + "A2058_SKIN", + "A253_SALIVARY_GLAND", + "A2780_OVARY", + "A375_SKIN", + "A3KAW_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "A549_LUNG", + "A673_BONE", + "ABC1_LUNG", + "ACCMESO1_PLEURA", + "AGS_STOMACH", + "AM38_CENTRAL_NERVOUS_SYSTEM", + "AML193_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "AMO1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "ASPC1_PANCREAS", + "AU565_BREAST", + "BC3C_URINARY_TRACT", + "BCPAP_THYROID", + "BECKER_CENTRAL_NERVOUS_SYSTEM", + "BFTC905_URINARY_TRACT", + "BFTC909_KIDNEY", + "BHT101_THYROID", + "BHY_UPPER_AERODIGESTIVE_TRACT", + "BICR16_UPPER_AERODIGESTIVE_TRACT", + "BICR22_UPPER_AERODIGESTIVE_TRACT", + "BICR31_UPPER_AERODIGESTIVE_TRACT", + "BICR56_UPPER_AERODIGESTIVE_TRACT", + "BICR6_UPPER_AERODIGESTIVE_TRACT", + "BL70_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "BT549_BREAST", + "BXPC3_PANCREAS", + "C2BBE1_LARGE_INTESTINE", + "C32_SKIN", + "CADOES1_BONE", + "CAKI1_KIDNEY", + "CAKI2_KIDNEY", + "CAL120_BREAST", + "CAL12T_LUNG", + "CAL27_UPPER_AERODIGESTIVE_TRACT", + "CAL29_URINARY_TRACT", + "CAL33_UPPER_AERODIGESTIVE_TRACT", + "CAL51_BREAST", + "CAL78_BONE", + "CALU1_LUNG", + "CALU6_LUNG", + "CAMA1_BREAST", + "CAOV3_OVARY", + "CAOV4_OVARY", + "CAPAN1_PANCREAS", + "CCFSTTG1_CENTRAL_NERVOUS_SYSTEM", + "CCK81_LARGE_INTESTINE", + "CFPAC1_PANCREAS", + "CHAGOK1_LUNG", + "CHP212_AUTONOMIC_GANGLIA", + "CJM_SKIN", + "CL11_LARGE_INTESTINE", + "CL40_LARGE_INTESTINE", + "CMK115_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "CMLT1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "COLO320_LARGE_INTESTINE", + "COLO678_LARGE_INTESTINE", + "COLO679_SKIN", + "COLO680N_OESOPHAGUS", + "COLO684_ENDOMETRIUM", + "COLO792_SKIN", + "COLO800_SKIN", + "CORL105_LUNG", + "CORL23_LUNG", + "CORL279_LUNG", + "CORL311_LUNG", + "CORL47_LUNG", + "COV318_OVARY", + "COV362_OVARY", + "COV434_OVARY", + "COV504_OVARY", + "COV644_OVARY", + "D283MED_CENTRAL_NERVOUS_SYSTEM", + "D341MED_CENTRAL_NERVOUS_SYSTEM", + "DANG_PANCREAS", + "DAOY_CENTRAL_NERVOUS_SYSTEM", + "DB_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "DBTRG05MG_CENTRAL_NERVOUS_SYSTEM", + "DEL_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "DETROIT562_UPPER_AERODIGESTIVE_TRACT", + "DKMG_CENTRAL_NERVOUS_SYSTEM", + "DLD1_LARGE_INTESTINE", + "DMS273_LUNG", + "DMS53_LUNG", + "DU145_PROSTATE", + "DU4475_BREAST", + "EBC1_LUNG", + "ECGI10_OESOPHAGUS", + "EFE184_ENDOMETRIUM", + "EFM19_BREAST", + "EFO21_OVARY", + "EFO27_OVARY", + "EJM_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "EKVX_LUNG", + "EM2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "EN_ENDOMETRIUM", + "EOL1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "EPLC272H_LUNG", + "ESS1_ENDOMETRIUM", + "EW8_BONE", + "EWS502_BONE", + "F36P_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "F5_CENTRAL_NERVOUS_SYSTEM", + "FADU_UPPER_AERODIGESTIVE_TRACT", + "FTC133_THYROID", + "FU97_STOMACH", + "G292CLONEA141B1_BONE", + "G401_SOFT_TISSUE", + "G402_SOFT_TISSUE", + "GAMG_CENTRAL_NERVOUS_SYSTEM", + "GB1_CENTRAL_NERVOUS_SYSTEM", + "GCT_SOFT_TISSUE", + "GI1_CENTRAL_NERVOUS_SYSTEM", + "GSS_STOMACH", + "GSU_STOMACH", + "H4_CENTRAL_NERVOUS_SYSTEM", + "HARA_LUNG", + "HCC1143_BREAST", + "HCC1187_BREAST", + "HCC1359_LUNG", + "HCC1395_BREAST", + "HCC1419_BREAST", + "HCC1428_BREAST", + "HCC15_LUNG", + "HCC1806_BREAST", + "HCC1937_BREAST", + "HCC1954_BREAST", + "HCC202_BREAST", + "HCC33_LUNG", + "HCC366_LUNG", + "HCC38_BREAST", + "HCC44_LUNG", + "HCC56_LARGE_INTESTINE", + "HCC70_BREAST", + "HCC78_LUNG", + "HCC827_LUNG", + "HCC95_LUNG", + "HCT116_LARGE_INTESTINE", + "HCT15_LARGE_INTESTINE", + "HDMYZ_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "HEC151_ENDOMETRIUM", + "HEC1B_ENDOMETRIUM", + "HEC251_ENDOMETRIUM", + "HEC265_ENDOMETRIUM", + "HEC50B_ENDOMETRIUM", + "HEC59_ENDOMETRIUM", + "HEC6_ENDOMETRIUM", + "HEL_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "HEL9217_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "HEPG2_LIVER", + "HEYA8_OVARY", + "HGC27_STOMACH", + "HLF_LIVER", + "HMC18_BREAST", + "HOP62_LUNG", + "HPAFII_PANCREAS", + "HS294T_SKIN", + "HS578T_BREAST", + "HS683_CENTRAL_NERVOUS_SYSTEM", + "HS695T_SKIN", + "HS729_SOFT_TISSUE", + "HS746T_STOMACH", + "HS766T_PANCREAS", + "HS852T_SKIN", + "HS936T_SKIN", + "HS944T_SKIN", + "HSC2_UPPER_AERODIGESTIVE_TRACT", + "HSC3_UPPER_AERODIGESTIVE_TRACT", + "HSC4_UPPER_AERODIGESTIVE_TRACT", + "HT_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "HT115_LARGE_INTESTINE", + "HT1197_URINARY_TRACT", + "HT1376_URINARY_TRACT", + "HT144_SKIN", + "HT29_LARGE_INTESTINE", + "HT55_LARGE_INTESTINE", + "HUCCT1_BILIARY_TRACT", + "HUG1N_STOMACH", + "HUH1_LIVER", + "HUH6_LIVER", + "HUH7_LIVER", + "HUNS1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "HUPT3_PANCREAS", + "HUPT4_PANCREAS", + "IALM_LUNG", + "IGR1_SKIN", + "IGR39_SKIN", + "IGROV1_OVARY", + "IM95_STOMACH", + "IMR32_AUTONOMIC_GANGLIA", + "IPC298_SKIN", + "ISHIKAWAHERAKLIO02ER_ENDOMETRIUM", + "ISTMES1_PLEURA", + "ISTMES2_PLEURA", + "J82_URINARY_TRACT", + "JHESOAD1_OESOPHAGUS", + "JHH1_LIVER", + "JHH2_LIVER", + "JHH4_LIVER", + "JHH5_LIVER", + "JHH6_LIVER", + "JHH7_LIVER", + "JHOC5_OVARY", + "JHOM1_OVARY", + "JHOS2_OVARY", + "JHOS4_OVARY", + "JHUEM1_ENDOMETRIUM", + "JHUEM7_ENDOMETRIUM", + "JIMT1_BREAST", + "JJN3_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "JM1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "JMSU1_URINARY_TRACT", + "JURKAT_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "JURLMK1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "JVM2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "JVM3_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "K029AX_SKIN", + "K562_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KALS1_CENTRAL_NERVOUS_SYSTEM", + "KARPAS299_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KARPAS422_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KASUMI1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KATOIII_STOMACH", + "KCIMOH1_PANCREAS", + "KCL22_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KE37_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KE39_STOMACH", + "KELLY_AUTONOMIC_GANGLIA", + "KIJK_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KLE_ENDOMETRIUM", + "KM12_LARGE_INTESTINE", + "KMBC2_URINARY_TRACT", + "KMM1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KMRC1_KIDNEY", + "KMRC2_KIDNEY", + "KMRC20_KIDNEY", + "KMRC3_KIDNEY", + "KMS11_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KMS26_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KMS27_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KMS34_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KNS42_CENTRAL_NERVOUS_SYSTEM", + "KNS60_CENTRAL_NERVOUS_SYSTEM", + "KNS62_LUNG", + "KNS81_CENTRAL_NERVOUS_SYSTEM", + "KO52_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KP2_PANCREAS", + "KP3_PANCREAS", + "KP4_PANCREAS", + "KPL1_BREAST", + "KPNRTBM1_AUTONOMIC_GANGLIA", + "KPNSI9S_AUTONOMIC_GANGLIA", + "KPNYN_AUTONOMIC_GANGLIA", + "KS1_CENTRAL_NERVOUS_SYSTEM", + "KU1919_URINARY_TRACT", + "KU812_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KURAMOCHI_OVARY", + "KYM1_SOFT_TISSUE", + "KYO1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "KYSE140_OESOPHAGUS", + "KYSE150_OESOPHAGUS", + "KYSE180_OESOPHAGUS", + "KYSE270_OESOPHAGUS", + "KYSE30_OESOPHAGUS", + "KYSE410_OESOPHAGUS", + "KYSE450_OESOPHAGUS", + "KYSE510_OESOPHAGUS", + "KYSE520_OESOPHAGUS", + "KYSE70_OESOPHAGUS", + "L1236_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "L33_PANCREAS", + "L363_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "L428_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "LCLC103H_LUNG", + "LCLC97TM1_LUNG", + "LI7_LIVER", + "LK2_LUNG", + "LMSU_STOMACH", + "LN18_CENTRAL_NERVOUS_SYSTEM", + "LN229_CENTRAL_NERVOUS_SYSTEM", + "LN235_CENTRAL_NERVOUS_SYSTEM", + "LN319_CENTRAL_NERVOUS_SYSTEM", + "LN340_CENTRAL_NERVOUS_SYSTEM", + "LN382_CENTRAL_NERVOUS_SYSTEM", + "LN443_CENTRAL_NERVOUS_SYSTEM", + "LNCAPCLONEFGC_PROSTATE", + "LNZ308_CENTRAL_NERVOUS_SYSTEM", + "LOUNH91_LUNG", + "LOVO_LARGE_INTESTINE", + "LP1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "LS1034_LARGE_INTESTINE", + "LS123_LARGE_INTESTINE", + "LS180_LARGE_INTESTINE", + "LS411N_LARGE_INTESTINE", + "LS513_LARGE_INTESTINE", + "LU65_LUNG", + "LU99_LUNG", + "LUDLU1_LUNG", + "LXF289_LUNG", + "M059K_CENTRAL_NERVOUS_SYSTEM", + "M07E_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MALME3M_SKIN", + "MC116_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MCAS_OVARY", + "MCF7_BREAST", + "MDAMB157_BREAST", + "MDAMB361_BREAST", + "MDAMB415_BREAST", + "MDAMB435S_SKIN", + "MDAMB436_BREAST", + "MDAMB453_BREAST", + "MDAMB468_BREAST", + "MDST8_LARGE_INTESTINE", + "MEC1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MELHO_SKIN", + "MELJUSO_SKIN", + "MFE280_ENDOMETRIUM", + "MFE296_ENDOMETRIUM", + "MFE319_ENDOMETRIUM", + "MG63_BONE", + "MHHES1_BONE", + "MHHNB11_AUTONOMIC_GANGLIA", + "MIAPACA2_PANCREAS", + "MJ_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MKN45_STOMACH", + "MKN74_STOMACH", + "MM1S_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MOLM13_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MONOMAC1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "MORCPR_LUNG", + "MPP89_PLEURA", + "MV411_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NALM6_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NAMALWA_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NB1_AUTONOMIC_GANGLIA", + "NB4_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NCIH1048_LUNG", + "NCIH1092_LUNG", + "NCIH1155_LUNG", + "NCIH1355_LUNG", + "NCIH1373_LUNG", + "NCIH1437_LUNG", + "NCIH1568_LUNG", + "NCIH1573_LUNG", + "NCIH1581_LUNG", + "NCIH1648_LUNG", + "NCIH1650_LUNG", + "NCIH1666_LUNG", + "NCIH1693_LUNG", + "NCIH1694_LUNG", + "NCIH1703_LUNG", + "NCIH1793_LUNG", + "NCIH1836_LUNG", + "NCIH1869_LUNG", + "NCIH1915_LUNG", + "NCIH1944_LUNG", + "NCIH1975_LUNG", + "NCIH2009_LUNG", + "NCIH2023_LUNG", + "NCIH2030_LUNG", + "NCIH2052_PLEURA", + "NCIH2087_LUNG", + "NCIH209_LUNG", + "NCIH211_LUNG", + "NCIH2110_LUNG", + "NCIH2122_LUNG", + "NCIH2126_LUNG", + "NCIH2170_LUNG", + "NCIH2171_LUNG", + "NCIH2172_LUNG", + "NCIH226_LUNG", + "NCIH2286_LUNG", + "NCIH2291_LUNG", + "NCIH23_LUNG", + "NCIH28_PLEURA", + "NCIH292_LUNG", + "NCIH358_LUNG", + "NCIH441_LUNG", + "NCIH446_LUNG", + "NCIH460_LUNG", + "NCIH510_LUNG", + "NCIH520_LUNG", + "NCIH522_LUNG", + "NCIH526_LUNG", + "NCIH647_LUNG", + "NCIH650_LUNG", + "NCIH661_LUNG", + "NCIH716_LARGE_INTESTINE", + "NCIH727_LUNG", + "NCIH747_LARGE_INTESTINE", + "NCIH82_LUNG", + "NCIH838_LUNG", + "NCIH841_LUNG", + "NCIN87_STOMACH", + "NCO2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NH6_AUTONOMIC_GANGLIA", + "NIHOVCAR3_OVARY", + "NMCG1_CENTRAL_NERVOUS_SYSTEM", + "NOMO1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "NUGC3_STOMACH", + "OAW28_OVARY", + "OAW42_OVARY", + "OC314_OVARY", + "OC316_OVARY", + "OCIAML2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "OCIAML3_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "OCILY19_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "OCUM1_STOMACH", + "OE21_OESOPHAGUS", + "OE33_OESOPHAGUS", + "ONCODG1_OVARY", + "ONS76_CENTRAL_NERVOUS_SYSTEM", + "OPM2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "OSRC2_KIDNEY", + "OUMS23_LARGE_INTESTINE", + "OV90_OVARY", + "OVCAR8_OVARY", + "OVISE_OVARY", + "OVK18_OVARY", + "OVMANA_OVARY", + "OVTOKO_OVARY", + "P31FUJ_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "PANC0203_PANCREAS", + "PANC0327_PANCREAS", + "PANC0403_PANCREAS", + "PANC0504_PANCREAS", + "PANC0813_PANCREAS", + "PANC1_PANCREAS", + "PANC1005_PANCREAS", + "PATU8902_PANCREAS", + "PATU8988S_PANCREAS", + "PATU8988T_PANCREAS", + "PC14_LUNG", + "PC3_PROSTATE", + "PECAPJ15_UPPER_AERODIGESTIVE_TRACT", + "PECAPJ34CLONEC12_UPPER_AERODIGESTIVE_TRACT", + "PECAPJ41CLONED2_UPPER_AERODIGESTIVE_TRACT", + "PECAPJ49_UPPER_AERODIGESTIVE_TRACT", + "PEER_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "PF382_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "PK1_PANCREAS", + "PLCPRF5_LIVER", + "PSN1_PANCREAS", + "RAJI_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RCC10RGB_KIDNEY", + "RCHACV_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RCM1_LARGE_INTESTINE", + "RD_SOFT_TISSUE", + "RDES_BONE", + "REH_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RERFGC1B_STOMACH", + "RERFLCAD1_LUNG", + "RERFLCAD2_LUNG", + "RERFLCSQ1_LUNG", + "RH30_SOFT_TISSUE", + "RH41_SOFT_TISSUE", + "RI1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RKN_SOFT_TISSUE", + "RKO_LARGE_INTESTINE", + "RL_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RL952_ENDOMETRIUM", + "RMGI_OVARY", + "RMUGS_OVARY", + "RPMI7951_SKIN", + "RPMI8226_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RS411_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "RT112_URINARY_TRACT", + "RT11284_URINARY_TRACT", + "RT4_URINARY_TRACT", + "RVH421_SKIN", + "SCABER_URINARY_TRACT", + "SCC25_UPPER_AERODIGESTIVE_TRACT", + "SCC4_UPPER_AERODIGESTIVE_TRACT", + "SCC9_UPPER_AERODIGESTIVE_TRACT", + "SEM_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SET2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SF172_CENTRAL_NERVOUS_SYSTEM", + "SF268_CENTRAL_NERVOUS_SYSTEM", + "SF295_CENTRAL_NERVOUS_SYSTEM", + "SF539_CENTRAL_NERVOUS_SYSTEM", + "SH10TC_STOMACH", + "SH4_SKIN", + "SHP77_LUNG", + "SHSY5Y_AUTONOMIC_GANGLIA", + "SIMA_AUTONOMIC_GANGLIA", + "SJSA1_BONE", + "SKBR3_BREAST", + "SKES1_BONE", + "SKHEP1_LIVER", + "SKM1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SKMEL2_SKIN", + "SKMEL24_SKIN", + "SKMEL30_SKIN", + "SKMES1_LUNG", + "SKMM2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SKNAS_AUTONOMIC_GANGLIA", + "SKNBE2_AUTONOMIC_GANGLIA", + "SKNDZ_AUTONOMIC_GANGLIA", + "SKNFI_AUTONOMIC_GANGLIA", + "SKNMC_BONE", + "SKNO1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SKNSH_AUTONOMIC_GANGLIA", + "SKOV3_OVARY", + "SKRC31_KIDNEY", + "SKUT1_SOFT_TISSUE", + "SLR20_KIDNEY", + "SLR24_KIDNEY", + "SNB75_CENTRAL_NERVOUS_SYSTEM", + "SNGM_ENDOMETRIUM", + "SNU1_STOMACH", + "SNU1033_LARGE_INTESTINE", + "SNU1076_UPPER_AERODIGESTIVE_TRACT", + "SNU1077_ENDOMETRIUM", + "SNU1079_BILIARY_TRACT", + "SNU1105_CENTRAL_NERVOUS_SYSTEM", + "SNU1196_BILIARY_TRACT", + "SNU16_STOMACH", + "SNU182_LIVER", + "SNU201_CENTRAL_NERVOUS_SYSTEM", + "SNU213_PANCREAS", + "SNU216_STOMACH", + "SNU245_BILIARY_TRACT", + "SNU308_BILIARY_TRACT", + "SNU349_KIDNEY", + "SNU387_LIVER", + "SNU398_LIVER", + "SNU410_PANCREAS", + "SNU449_LIVER", + "SNU46_UPPER_AERODIGESTIVE_TRACT", + "SNU475_LIVER", + "SNU478_BILIARY_TRACT", + "SNU5_STOMACH", + "SNU503_LARGE_INTESTINE", + "SNU601_STOMACH", + "SNU61_LARGE_INTESTINE", + "SNU620_STOMACH", + "SNU626_CENTRAL_NERVOUS_SYSTEM", + "SNU719_STOMACH", + "SNU738_CENTRAL_NERVOUS_SYSTEM", + "SNU761_LIVER", + "SNU8_OVARY", + "SNU81_LARGE_INTESTINE", + "SNU840_OVARY", + "SNU869_BILIARY_TRACT", + "SNUC1_LARGE_INTESTINE", + "SNUC4_LARGE_INTESTINE", + "SNUC5_LARGE_INTESTINE", + "SR786_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "ST486_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SU8686_PANCREAS", + "SUDHL1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUDHL10_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUDHL4_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUDHL5_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUDHL8_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUIT2_PANCREAS", + "SUPM2_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SUPT1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "SW1088_CENTRAL_NERVOUS_SYSTEM", + "SW1116_LARGE_INTESTINE", + "SW1271_LUNG", + "SW1463_LARGE_INTESTINE", + "SW1573_LUNG", + "SW1990_PANCREAS", + "SW403_LARGE_INTESTINE", + "SW48_LARGE_INTESTINE", + "SW620_LARGE_INTESTINE", + "SW837_LARGE_INTESTINE", + "T3M4_PANCREAS", + "T47D_BREAST", + "T84_LARGE_INTESTINE", + "TC71_BONE", + "TCCPAN2_PANCREAS", + "TCCSUP_URINARY_TRACT", + "TE1_OESOPHAGUS", + "TE10_OESOPHAGUS", + "TE11_OESOPHAGUS", + "TE4_OESOPHAGUS", + "TE5_OESOPHAGUS", + "TE6_OESOPHAGUS", + "TE8_OESOPHAGUS", + "TE9_OESOPHAGUS", + "TEN_ENDOMETRIUM", + "TF1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "THP1_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "TM31_CENTRAL_NERVOUS_SYSTEM", + "TOV112D_OVARY", + "TOV21G_OVARY", + "TT_OESOPHAGUS", + "TT2609C02_THYROID", + "TUHR10TKB_KIDNEY", + "TUHR14TKB_KIDNEY", + "TUHR4TKB_KIDNEY", + "TYKNU_OVARY", + "U118MG_CENTRAL_NERVOUS_SYSTEM", + "U178_CENTRAL_NERVOUS_SYSTEM", + "U251MG_CENTRAL_NERVOUS_SYSTEM", + "U2OS_BONE", + "U343_CENTRAL_NERVOUS_SYSTEM", + "U87MG_CENTRAL_NERVOUS_SYSTEM", + "U937_HAEMATOPOIETIC_AND_LYMPHOID_TISSUE", + "UACC257_SKIN", + "UACC62_SKIN", + "UACC893_BREAST", + "UMUC1_URINARY_TRACT", + "UMUC3_URINARY_TRACT", + "UOK101_KIDNEY", + "VCAP_PROSTATE", + "VMCUB1_URINARY_TRACT", + "VMRCRCW_KIDNEY", + "WM115_SKIN", + "WM1799_SKIN", + "WM2664_SKIN", + "WM793_SKIN", + "WM88_SKIN", + "WM983B_SKIN", + "YAPC_PANCREAS", + "YD15_SALIVARY_GLAND", + "YD38_UPPER_AERODIGESTIVE_TRACT", + "YD8_UPPER_AERODIGESTIVE_TRACT", + "YH13_CENTRAL_NERVOUS_SYSTEM", + "YKG1_CENTRAL_NERVOUS_SYSTEM", + "ZR751_BREAST", +] + +rule all: + input: + "processed/shared_cell_lines.txt", + "processed/empty_cell_lines.csv", + "processed/final_cell_lines.csv", + "processed/interactome.tsv", + expand("processed/{ccle_id}/input_nodes.csv", ccle_id=CELL_LINES), + expand("processed/{ccle_id}/input_nodes_no_cna.csv", ccle_id=CELL_LINES), + expand("processed/{ccle_id}/gold_standard.csv", ccle_id=CELL_LINES), + +produce_fetch_rules({ + + # depmap data + "raw/CRISPRGeneDependency_25Q3.csv": ("DepMap_25Q3", "CRISPRGeneDependency_25Q3.csv"), + "raw/OmicsSomaticMutationsMatrixDamaging_25Q3.csv": ("DepMap_25Q3", "OmicsSomaticMutationsMatrixDamaging_25Q3.csv"), + + # cbioportal data + "raw/data_cna_cbioportal_ccle2019.txt": ("cbioportal", "data_cna_cbioportal_ccle2019.txt"), + + # tfa data + "raw/consensus_tfa_march_6.tsv": ("estimateNCA", "consensus_tfa_march_6.tsv"), + + # for shared lines across all raw data + "raw/Model_25Q3.csv": ("DepMap_25Q3", "Model_25Q3.csv"), + "raw/Cell_lines_annotations_20181226_ccle2019.txt" : ("CCLE_2019", "Cell_lines_annotations_20181226_ccle2019.txt"), + + "raw/9606.protein.physical.links.full.txt": FetchConfig(("STRING", "v12", "9606", "9606.protein.physical.links.v12.0.txt.gz"), post_process=PostProcessAction.UNCOMPRESS_GZ), + "raw/9606.protein.aliases.txt": FetchConfig(("STRING", "v12", "9606", "9606.protein.aliases.txt.gz"), post_process=PostProcessAction.UNCOMPRESS_GZ), + +}) + +rule shared_cell_lines: + input: + "raw/Model_25Q3.csv", + "raw/Cell_lines_annotations_20181226_ccle2019.txt", + "raw/consensus_tfa_march_6.tsv", + "raw/CRISPRGeneDependency_25Q3.csv", + "raw/OmicsSomaticMutationsMatrixDamaging_25Q3.csv", + "raw/data_cna_cbioportal_ccle2019.txt" + output: + "processed/shared_cell_lines.txt" + shell: + "uv run scripts/find_shared_cell_lines.py" + +rule interactome: + input: + "raw/9606.protein.physical.links.full.txt", + "raw/9606.protein.aliases.txt" + output: + "processed/interactome.tsv", + shell: + "uv run scripts/process_interactome.py" + +rule input_nodes: + input: + "raw/data_cna_cbioportal_ccle2019.txt", + "raw/consensus_tfa_march_6.tsv", + "raw/OmicsSomaticMutationsMatrixDamaging_25Q3.csv", + "processed/shared_cell_lines.txt", + "raw/9606.protein.aliases.txt", + "processed/interactome.tsv", + output: + expand("processed/{ccle_id}/input_nodes.csv", ccle_id=CELL_LINES), + expand("processed/{ccle_id}/input_nodes_no_cna.csv", ccle_id=CELL_LINES), + "processed/empty_cell_lines.csv", + + shell: + "uv run scripts/process_input_nodes.py" + +rule gold_standard: + input: + "raw/CRISPRGeneDependency_25Q3.csv", + "processed/interactome.tsv", + "raw/9606.protein.aliases.txt", + "processed/shared_cell_lines.txt", + "processed/empty_cell_lines.csv", + output: + expand("processed/{ccle_id}/gold_standard.csv", ccle_id=CELL_LINES), + "processed/final_cell_lines.csv" + shell: + "uv run scripts/process_gold_standard.py" \ No newline at end of file diff --git a/datasets/depmap/scripts/find_shared_cell_lines.py b/datasets/depmap/scripts/find_shared_cell_lines.py new file mode 100644 index 0000000..22c7360 --- /dev/null +++ b/datasets/depmap/scripts/find_shared_cell_lines.py @@ -0,0 +1,118 @@ +import pandas as pd +import numpy as np +from pathlib import Path + +DEPMAP_DIR = Path(__file__).parent.resolve() / ".." +RAW_DIR = DEPMAP_DIR / "raw" +PROCESSED_DIR = DEPMAP_DIR / "processed" + +def get_tfs_beyond_thresholds(): + """ + Read TFA data and get TFs beyond 2 SD thresholds per cell line. + """ + + df_tfa = pd.read_csv(RAW_DIR / "consensus_tfa_march_6.tsv", sep="\t", index_col=0) + + mu = df_tfa.mean(axis=1) + sigma = df_tfa.std(axis=1).replace(0, np.nan) + df_zscore = df_tfa.sub(mu, axis=0).div(sigma, axis=0) + + results = {} + for threshold, label in [(2, "2sd")]: + rows = [] + for cell_line in df_zscore.columns: + z_vals = df_zscore[cell_line].values + z_vals = z_vals[~np.isnan(z_vals)] + + # Count TFs beyond threshold + mask = np.abs(z_vals) > threshold + n_tfs = np.sum(mask) + + rows.append({"cell_line": cell_line, "n_tfs": n_tfs}) + + results[label] = pd.DataFrame(rows).sort_values("n_tfs", ascending=False) + + return results + + +def main(): + PROCESSED_DIR.mkdir(exist_ok=True) + + df_cna = pd.read_csv(RAW_DIR / "data_cna_cbioportal_ccle2019.txt", sep="\t", index_col=-0) + df_cna.index.name = None # removes the hugo_symbol index name + df_somatic = pd.read_csv(RAW_DIR / "OmicsSomaticMutationsMatrixDamaging_25Q3.csv", index_col=0) + df_somatic = df_somatic.set_index('ModelID').drop(columns=['SequencingID', 'ModelConditionID','IsDefaultEntryForModel', 'IsDefaultEntryForMC']) + df_crispr_genes = pd.read_csv(RAW_DIR / "CRISPRGeneDependency_25Q3.csv", sep=",", index_col=0, header=0) + + # Get TFs beyond 2 SD + tf_results = get_tfs_beyond_thresholds() + df_tfs = tf_results["2sd"] # Use 2SD for shared cell line filtering + + # ccle id -> depmap id; comes from CCLE 2019 + cell_line_annotations = pd.read_csv(RAW_DIR / "Cell_lines_annotations_20181226_ccle2019.txt", sep="\t", header=0) + cell_line_annotations = cell_line_annotations[["CCLE_ID", "depMapID"]].dropna(subset=["CCLE_ID", "depMapID"]) + + # depmap id -> ccle id; comes from DepMap 25Q3 + model_annotations = pd.read_csv(RAW_DIR / "Model_25Q3.csv") + model_annotations = model_annotations[["ModelID", "CCLEName"]] + + # converting CCLE cell line ids to depmap cell line ids using cell_line_annotations (from ccle 2019) + cell_line_annotations_ccle_to_depmap_map = dict(zip(cell_line_annotations["CCLE_ID"], cell_line_annotations["depMapID"])) + df_cna = df_cna.rename(columns=cell_line_annotations_ccle_to_depmap_map) + + # removing columns from df_cna with no matching depMapID + matched_cols = [col for col in df_cna.columns if col.startswith("ACH-")] + df_cna = df_cna[matched_cols] + + # remove any cell lines with no copy number alterations (no 2s or -2s) + cna = df_cna.isin([2, -2]).any(axis=0) + df_cna = df_cna.loc[:, cna] + + # remove any cell lines with no somatic mutations (all < 1.0) + + has_mutation = (df_somatic >= 1.0).any(axis=1) + df_somatic = df_somatic.loc[has_mutation, :] + + # remove any cell lines with no tfs + df_tfs = df_tfs[df_tfs["n_tfs"] >= 1] + + # remove any cell lines with no crispr genes >=0.5 + has_crispr = (df_crispr_genes >= 0.5).any(axis=1) + df_crispr_genes = df_crispr_genes.loc[has_crispr, :] + + # find all cell lines that match between all of the data + # all are depmap ids + cna_cell_lines = set(df_cna.columns) + somatic_cell_lines = set(df_somatic.index) + tfs_cell_lines = set(df_tfs["cell_line"]) + crispr_cell_lines = set(df_crispr_genes.index) + + shared_cell_lines = cna_cell_lines & somatic_cell_lines & tfs_cell_lines & crispr_cell_lines + + # Check which cell line annotations and model.csv ids don't having matching depmap ids and ccle ids + # all of those will be marked as problematic + + # same CCLE ID, different depmap ID + merged_on_ccle = cell_line_annotations.merge(model_annotations, left_on="CCLE_ID", right_on="CCLEName", how="inner") + ccle_depmap_mismatch = merged_on_ccle[merged_on_ccle["depMapID"] != merged_on_ccle["ModelID"]] + + # same depmap ID, different CCLE ID + merged_on_depmap = cell_line_annotations.merge(model_annotations, left_on="depMapID", right_on="ModelID", how="inner") + depmap_ccle_mismatch = merged_on_depmap[merged_on_depmap["CCLE_ID"] != merged_on_depmap["CCLEName"]] + + # union of all problematic depmap IDs + problematic_depmap_ids = set(ccle_depmap_mismatch["depMapID"]) | set(ccle_depmap_mismatch["ModelID"]) | set(depmap_ccle_mismatch["depMapID"]) + + # remove problematic dep map ids from total number of shared_cell_lines + shared_cell_lines = shared_cell_lines - problematic_depmap_ids + + shared_df = ( + model_annotations[model_annotations["ModelID"].isin(shared_cell_lines)] + .sort_values("ModelID") + .rename(columns={"ModelID": "depmap_id", "CCLEName": "ccle_id"}) + ) + shared_df.to_csv(PROCESSED_DIR / "shared_cell_lines.txt", index=False, sep= "\t") + print(f"Finished getting shared cell lines: {len(shared_cell_lines)}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/datasets/depmap/scripts/preprocess_scripts/README.md b/datasets/depmap/scripts/preprocess_scripts/README.md new file mode 100644 index 0000000..52e6a5b --- /dev/null +++ b/datasets/depmap/scripts/preprocess_scripts/README.md @@ -0,0 +1,5 @@ +This will hold the scripts and code that lead to the files that are being processed + +# tf code + +TODO: Still need to add \ No newline at end of file diff --git a/datasets/depmap/scripts/process_gold_standard.py b/datasets/depmap/scripts/process_gold_standard.py new file mode 100644 index 0000000..f598320 --- /dev/null +++ b/datasets/depmap/scripts/process_gold_standard.py @@ -0,0 +1,83 @@ +import pandas as pd +from pathlib import Path +from tools.normalize.trim_list import trim_node_list +from tools.normalize.interactome import get_interactome_nodes +from util import build_hgnc_to_ensp + +DEPMAP_DIR = Path(__file__).parent.resolve() / ".." +RAW_DIR = DEPMAP_DIR / "raw" +PREPROCESSED_DIR = DEPMAP_DIR / "preprocessed" +PROCESSED_DIR = DEPMAP_DIR / "processed" + +DEPENDENCY_CUTOFF = 0.5 + +def main(): + PROCESSED_DIR.mkdir(exist_ok=True) + + df_idmapping = pd.read_csv(RAW_DIR / "9606.protein.aliases.txt", sep="\t") + + df_interactome = pd.read_csv(PROCESSED_DIR / "interactome.tsv", sep="\t", header=None, names=["Interactor1", "Interactor2", "Weight", "Direction"]) + + df_chosen_cell_lines = pd.read_csv(PROCESSED_DIR / "shared_cell_lines.txt", sep="\t") + + # drop cell line if empty + df_empty = pd.read_csv(PROCESSED_DIR / "empty_cell_lines.csv", sep="\t") + drop_ccle = set(df_empty["ccle_id"]) + df_chosen_cell_lines = df_chosen_cell_lines[~df_chosen_cell_lines["ccle_id"].isin(drop_ccle)] + + df_gs = pd.read_csv(RAW_DIR / "CRISPRGeneDependency_25Q3.csv", sep=",", index_col=0, header=0) + df_gs.index.name = "depmap_id" + + df_gs = df_gs.merge( + df_chosen_cell_lines, left_on="depmap_id", right_on="depmap_id", how="inner" + ).drop(columns=["depmap_id"]) + + # strip "(NCBI)" suffix from gene-name columns + df_gs.columns = df_gs.columns.str.replace(r"\s*\(.*\)$", "", regex=True) + df_gs = df_gs.set_index("ccle_id") + + # change the hgnc -> ensp + hgnc_to_ensp = build_hgnc_to_ensp(df_idmapping) + mapped_cols = [c for c in df_gs.columns if c in hgnc_to_ensp] + df_gs = df_gs[mapped_cols].rename(columns=hgnc_to_ensp) + + interactome_nodes = get_interactome_nodes(df_interactome) + empty_gs_ccle_ids = [] + + for ccle_id, row in df_gs.iterrows(): + passing = row[row > DEPENDENCY_CUTOFF].index.tolist() + + trimmed = trim_node_list(interactome_nodes, passing) + + if not trimmed: + print(f"{ccle_id} is an empty gold standard after interactome trim ({len(passing)} passing before trim)") + empty_gs_ccle_ids.append(ccle_id) + continue + + out_dir = PROCESSED_DIR / ccle_id + out_dir.mkdir(parents=True, exist_ok=True) + out_path = out_dir / "gold_standard.csv" + + pd.DataFrame({"NODEID": sorted(trimmed)}).to_csv(out_path, sep="\t", index=False) + + print("Finished processing gold standard data") + + # write out the list of ccle_ids with empty gs after trimming + df_final_cell_lines = df_chosen_cell_lines[~df_chosen_cell_lines["ccle_id"].isin(empty_gs_ccle_ids)] + final_path = PROCESSED_DIR / "final_cell_lines.csv" + df_final_cell_lines.to_csv(final_path, sep="\t", index=False) + + # append empty gold standard cell lines to empty_cell_lines.csv + if empty_gs_ccle_ids: + df_empty_gs = pd.DataFrame({ + "ccle_id": empty_gs_ccle_ids, + "version": "gold_standard", + "empty_data": "no gold standard nodes after interactome trim" + }) + empty_path = PROCESSED_DIR / "empty_cell_lines.csv" + df_empty_gs.to_csv(empty_path, sep="\t", index=False, mode="a", header= not empty_path.exists()) + + print(f"\n{len(empty_gs_ccle_ids)} cell lines dropped due to empty gs; {len(df_final_cell_lines)} cell lines written to {final_path}") + +if __name__ == "__main__": + main() diff --git a/datasets/depmap/scripts/process_input_nodes.py b/datasets/depmap/scripts/process_input_nodes.py new file mode 100644 index 0000000..9c1601f --- /dev/null +++ b/datasets/depmap/scripts/process_input_nodes.py @@ -0,0 +1,195 @@ +import pandas as pd +from pathlib import Path +from tools.normalize.trim_input_nodes import trim_input_nodes_file +from util import build_hgnc_to_ensp + +DEPMAP_DIR = Path(__file__).parent.resolve() / ".." +RAW_DIR = DEPMAP_DIR / "raw" +PREPROCESSED_DIR = DEPMAP_DIR / "preprocessed" +PROCESSED_DIR = DEPMAP_DIR / "processed" + + +def preprocess_cna(df_cna: pd.DataFrame, df_chosen_cell_lines: pd.DataFrame, hgnc_to_ensp: dict[str, str]) -> None: + """ + Write per-cell-line CNA scores (genes with score in {-2, 2} -> prize of 0.5). + """ + + # restrict to chosen cell lines + chosen_ccle_ids = set(df_chosen_cell_lines["ccle_id"]) + df_cna = df_cna[[c for c in df_cna.columns if c in chosen_ccle_ids]] + # map gene ids HGNC_symbol -> ENSP + df_cna = df_cna.rename(index=hgnc_to_ensp) + df_cna = df_cna[df_cna.index.isin(hgnc_to_ensp.values())] + + for cell_line in df_cna.columns: + scores = df_cna[cell_line] + filtered = scores[scores.isin([-2, 2])].astype(float) + filtered[:] = 0.5 + + cell_dir = PREPROCESSED_DIR / cell_line + cell_dir.mkdir(parents=True, exist_ok=True) + filtered.rename("prize").rename_axis("NODEID").to_csv(cell_dir / "cna.csv", sep="\t", header=True) + + print("Finished preprocessing CNA input node data") + + +def preprocess_somatic_mutations(df_sm: pd.DataFrame, df_chosen_cell_lines: pd.DataFrame, hgnc_to_ensp: dict[str, str]) -> None: + """Write per-cell-line damaging somatic mutation scores (>= 1.0).""" + df_sm = df_sm.drop(columns=["SequencingID", "ModelConditionID", "IsDefaultEntryForMC"]) + df_sm = df_sm[df_sm["IsDefaultEntryForModel"] == "Yes"] + + df_sm = df_sm.merge( + df_chosen_cell_lines, left_on="ModelID", right_on="depmap_id", how="inner" + ).drop(columns=["depmap_id", "IsDefaultEntryForModel", "ModelID"]) + + # strip "(NCBI)" suffix from gene-name columns + df_sm.columns = df_sm.columns.str.replace(r"\s*\(.*\)$", "", regex=True) + df_sm = df_sm.set_index("ccle_id") + + # map HGNC -> ENSP, dropping unmapped columns + mapped_cols = [c for c in df_sm.columns if c in hgnc_to_ensp] + df_sm = df_sm[mapped_cols].rename(columns=hgnc_to_ensp) + + for cell_line in df_sm.index: + scores = df_sm.loc[cell_line] + filtered = scores[scores >= 1.0] + + cell_dir = PREPROCESSED_DIR / cell_line + cell_dir.mkdir(parents=True, exist_ok=True) + filtered.rename("prize").rename_axis("NODEID").to_csv( + cell_dir / "sm.csv", sep="\t", header=True + ) + + print("Finished preprocessing somatic mutation input node data") + + +def preprocess_tf_activity(df_tfa: pd.DataFrame, df_chosen_cell_lines: pd.DataFrame, hgnc_to_ensp: dict[str, str]) -> None: + """Write per-cell-line TF activity scores for TFs whose |z-score| exceeds 2.0 (2 standard). + + Z-scores are computed per TF (row-wise) across all chosen cell lines. + """ + # remap columns: depmap_id -> ccle_id, keeping only chosen cell lines + depmap_to_ccle = dict(zip(df_chosen_cell_lines["depmap_id"], df_chosen_cell_lines["ccle_id"])) + df_tfa = df_tfa[[c for c in df_tfa.columns if c in depmap_to_ccle]].rename(columns=depmap_to_ccle) + + # remap index: HGNC_symbol -> ENSP + df_tfa = df_tfa.rename(index=hgnc_to_ensp) + df_tfa = df_tfa[df_tfa.index.isin(hgnc_to_ensp.values())] + + # per-TF z-score: subtract row mean, divide by row std + df_zscore = df_tfa.sub(df_tfa.mean(axis=1), axis=0).div(df_tfa.std(axis=1), axis=0) + + # for each cell line, keep TFs with |z| > 2sd; score is absolute raw TFA + for ccle_id in df_zscore.columns: + mask = df_zscore[ccle_id].abs() > 2.0 + scores = df_tfa.loc[mask, ccle_id].abs() + + cell_dir = PREPROCESSED_DIR / ccle_id + cell_dir.mkdir(parents=True, exist_ok=True) + scores.rename("prize").rename_axis("NODEID").to_csv(cell_dir / "tf.csv", sep="\t", header=True) + + print("Finished preprocessing transcription factor input node data") + +def build_spras_node_table(ccle_id: str, df_interactome: pd.DataFrame) -> None: + """Combine per-cell-line preprocessed inputs into SPRAS node tables (with and without CNA).""" + cell_dir = PREPROCESSED_DIR / ccle_id + + sm_path = cell_dir / "sm.csv" + tf_path = cell_dir / "tf.csv" + + # read inputs + sm = pd.read_csv(sm_path, sep="\t") + tf = pd.read_csv(tf_path, sep="\t") + + sm["sources"], sm["targets"] = True, False + tf["sources"], tf["targets"] = False, True + + # process with CNA + cna_path = cell_dir / "cna.csv" + cna = pd.read_csv(cna_path, sep="\t") + cna["sources"], cna["targets"] = True, False + + frames_with_cna = [sm, tf, cna] + frames_with_cna = [df for df in frames_with_cna if not df.empty] + combined_with_cna = pd.concat(frames_with_cna, ignore_index=True) + + # collapse duplicates: keep the max prize and OR the role flags + agg_with_cna = combined_with_cna.groupby("NODEID", as_index=False).agg( + prize=("prize", "max"), + sources=("sources", "any"), + targets=("targets", "any"), + ) + agg_with_cna["active"] = True + agg_with_cna = agg_with_cna[["NODEID", "sources", "targets", "prize", "active"]] + + # trim with interactome + trimmed_with_cna = trim_input_nodes_file(df_interactome, agg_with_cna) + + # process without CNA + frames_no_cna = [sm, tf] + frames_no_cna = [df for df in frames_no_cna if not df.empty] + combined_no_cna = pd.concat(frames_no_cna, ignore_index=True) + + agg_no_cna = combined_no_cna.groupby("NODEID", as_index=False).agg( + prize=("prize", "max"), + sources=("sources", "any"), + targets=("targets", "any"), + ) + agg_no_cna["active"] = True + agg_no_cna = agg_no_cna[["NODEID", "sources", "targets", "prize", "active"]] + + trimmed_no_cna = trim_input_nodes_file(df_interactome, agg_no_cna) + + # Check both versions for empty data + empty_rows = [] + out_dir = PROCESSED_DIR / ccle_id + out_dir.mkdir(parents=True, exist_ok=True) + + for trimmed, version_label in [(trimmed_with_cna, "input_nodes_with_cna"), (trimmed_no_cna, "input_nodes_without_cna")]: + n_sources = trimmed["sources"].sum() + n_targets = trimmed["targets"].sum() + if n_sources == 0 or n_targets == 0: + missing = [] + if n_sources == 0: + missing.append("sources") + if n_targets == 0: + missing.append("targets") + reason = " and ".join(missing) + empty_rows.append({"ccle_id": ccle_id, "version": version_label, "empty_data": reason}) + print(f"Skipping {ccle_id} ({version_label}): 0 {reason} after trimming") + else: + # Save the non-empty version + filename = "input_nodes.csv" if version_label == "input_nodes_with_cna" else "input_nodes_no_cna.csv" + trimmed.to_csv(out_dir / filename, sep="\t", index=False, header=True) + + if empty_rows: + empty_path = PROCESSED_DIR / "empty_cell_lines.csv" + pd.DataFrame(empty_rows).to_csv(empty_path, sep="\t", index=False, mode="a", header=not empty_path.exists()) + + +def main(): + PREPROCESSED_DIR.mkdir(exist_ok=True) + PROCESSED_DIR.mkdir(exist_ok=True) + + # load all raw inputs + df_idmapping = pd.read_csv(RAW_DIR / "9606.protein.aliases.txt", sep="\t") + df_chosen_cell_lines = pd.read_csv(PROCESSED_DIR / "shared_cell_lines.txt", sep="\t") + df_cna = pd.read_csv(RAW_DIR / "data_cna_cbioportal_ccle2019.txt", sep="\t", index_col=0) + df_sm = pd.read_csv(RAW_DIR / "OmicsSomaticMutationsMatrixDamaging_25Q3.csv", index_col=0) + df_tfa = pd.read_csv(RAW_DIR / "consensus_tfa_march_6.tsv", sep="\t", index_col=0) + df_interactome = pd.read_csv(PROCESSED_DIR / "interactome.tsv", sep="\t", header=None, names=["Interactor1", "Interactor2", "Weight", "Direction"]) + + hgnc_to_ensp = build_hgnc_to_ensp(df_idmapping) + + preprocess_cna(df_cna, df_chosen_cell_lines, hgnc_to_ensp) + preprocess_somatic_mutations(df_sm, df_chosen_cell_lines, hgnc_to_ensp) + preprocess_tf_activity(df_tfa, df_chosen_cell_lines, hgnc_to_ensp) + + for ccle_id in df_chosen_cell_lines["ccle_id"]: + build_spras_node_table(ccle_id=ccle_id, df_interactome=df_interactome) + + print("Finished processing input node data into SPRAS format") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/datasets/depmap/scripts/process_interactome.py b/datasets/depmap/scripts/process_interactome.py new file mode 100644 index 0000000..19168ed --- /dev/null +++ b/datasets/depmap/scripts/process_interactome.py @@ -0,0 +1,31 @@ +from pathlib import Path +import pandas + +from tools.normalize.interactome import deduplicate_edges + +depmap_directory = Path(__file__).parent.resolve() / ".." + + +def main(): + (depmap_directory / "processed").mkdir(exist_ok=True) + + interactome_df = pandas.read_csv(depmap_directory / "raw" / "9606.protein.physical.links.full.txt", sep=" ") + + interactome_df = interactome_df.rename(columns={"protein1": "Interactor1", "protein2": "Interactor2", "combined_score": "Weight"}) + interactome_df = interactome_df[interactome_df["Weight"] > 1] + interactome_df["Interactor1"] = interactome_df["Interactor1"].astype(str).str.removeprefix("9606.") + interactome_df["Interactor2"] = interactome_df["Interactor2"].astype(str).str.removeprefix("9606.") + interactome_df["Direction"] = "U" + + # deduplicate edges in the interactome + interactome_df, _ = deduplicate_edges(interactome_df) + interactome_df.sort_values('Weight',inplace=True) + + # save SPRAS formatted interactome + interactome_df.to_csv(depmap_directory / "processed" / "interactome.tsv", index=False, header=False, sep="\t") + + +if __name__ == "__main__": + main() + + diff --git a/datasets/depmap/scripts/util.py b/datasets/depmap/scripts/util.py new file mode 100644 index 0000000..d408f23 --- /dev/null +++ b/datasets/depmap/scripts/util.py @@ -0,0 +1,13 @@ +import pandas as pd + +def build_hgnc_to_ensp(idmapping_df: pd.DataFrame) -> dict[str, str]: + """ + Build an HGNC symbol -> ENSP id mapping from the STRING aliases file. + """ + + df_hgnc = ( + idmapping_df.loc[idmapping_df["source"] == "Ensembl_HGNC_symbol"] + .rename(columns={"#string_protein_id": "ENSP", "alias": "HGNC_symbol"}) + .assign(ENSP=lambda x: x["ENSP"].str.removeprefix("9606.")) + ) + return dict(zip(df_hgnc["HGNC_symbol"], df_hgnc["ENSP"])) diff --git a/datasets/egfr/.gitignore b/datasets/egfr/.gitignore new file mode 100644 index 0000000..b65d932 --- /dev/null +++ b/datasets/egfr/.gitignore @@ -0,0 +1,3 @@ +raw +preprocessed +processed diff --git a/datasets/egfr/README.md b/datasets/egfr/README.md new file mode 100644 index 0000000..5d71d1c --- /dev/null +++ b/datasets/egfr/README.md @@ -0,0 +1,31 @@ +# EGFR + +EGFR dataset. This dataset does a lot less processing for raw files, and is mainly focused on creating the new STRING-based interactome. + +## Overview + +This produces a dataset based off of the undirected STRING interactome with ENSP identifiers. + +## Data + +_See `Snakefile` for specific file locations._ + +The following data is from [_Synthesizing Signaling Pathways from Temporal Phosphoproteomic Data_](https://doi.org/10.1016/j.celrep.2018.08.085): +- Phosphoproteomic prize data (`egfr-prizes.txt`) +- Gold standard nodes (`eight-egfr-reference-all.txt`) + +We also use the [StringDB](https://string-db.org/) (v12) full links human interactome and +[UniProt](https://www.uniprot.org) mapping files which are based in ENSP IDs. See `cache/directory.py` and `./Snakefile` for more info on precisely +where these were fetched. + +## Scripts + +- `process_input_nodes.py`: produces a `input-nodes-uniprot.txt` from +[egfr-prizes.txt](https://raw.githubusercontent.com/gitter-lab/tps/refs/heads/master/data/pcsf/egfr-prizes.txt), +trimming psuedonodes, marking all nodes as active, and manually injecting the `EGF_HUMAN` +receptor as a source node (and dummy node for OmicsIntegrator1), while making all other nodes targets and dummy nodes. +- `process_interactome.py`: Produces the STRING `interactome.tsv` file from the STRING links file. +- `process_gold_standard.py`: Produces the `gold-standard-nodes-uniprot.txt` file from the [EGFR prize file](https://raw.githubusercontent.com/gitter-lab/tps/ca7cafd1e1c17f45ddea07c3fb54d0d70b86ff45/data/resources/eight-egfr-reference-all.txt) from the above paper. +- `map_ensembl.py`: Maps UniProt identifiers to STRING-restricted ENSP identifiers for the STRING-based data. + +We finalize this by trimming and otherwise normalizing the interactome, which is more precisely described in `tools/normalize/cli.py`. diff --git a/datasets/egfr/Snakefile b/datasets/egfr/Snakefile new file mode 100644 index 0000000..0fe76b0 --- /dev/null +++ b/datasets/egfr/Snakefile @@ -0,0 +1,75 @@ +include: "../../cache/Snakefile" + +rule all: + input: + # Our Ensembl protein based files over the STRING interactome + "processed/ensp/gold-standard-nodes.txt", + "processed/ensp/input-nodes.txt", + "processed/ensp/interactome.tsv", + +produce_fetch_rules({ + "raw/9606.protein.links.full.txt": FetchConfig( + ("STRING", "v12", "9606", "9606.protein.links.full.txt.gz"), + post_process=PostProcessAction.UNCOMPRESS_GZ + ), + "raw/HUMAN_9606_idmapping_selected.tsv": FetchConfig( + ("UniProt", "9606", "HUMAN_9606_idmapping_selected.tab.gz"), + post_process=PostProcessAction.UNCOMPRESS_GZ + ), + + # EGFR-specific files. + # The following files are from https://github.com/gitter-lab/tps. + # While the following files do point to the repository's main branch, + # they aren't expected to actually change. + "raw/eight-egfr-reference-all.txt": CacheItem( + name="EGFR Gold Standard Reference", + pinned="https://raw.githubusercontent.com/gitter-lab/tps/refs/heads/master/data/resources/eight-egfr-reference-all.txt", + cached="https://drive.google.com/uc?id=15MqpIbH1GRA1tq0ZXH9oMnKytoFSzXyw", + ), + "raw/egfr-prizes.txt": CacheItem( + name="EGFR prizes", + pinned="https://raw.githubusercontent.com/gitter-lab/tps/refs/heads/master/data/pcsf/egfr-prizes.txt", + cached="https://drive.google.com/uc?id=1nI5hw-rYRZPs15UJiqokHpHEAabRq6Xj", + ), +}) + +rule process_gold_standard: + input: "raw/eight-egfr-reference-all.txt" + output: "preprocessed/uniprot/gold-standard-nodes.txt", + shell: "uv run scripts/process_gold_standard.py" + +rule process_interactome: + input: "raw/9606.protein.links.full.txt" + output: "preprocessed/ensp/interactome.tsv" + shell: "uv run scripts/process_interactome.py" + +rule process_input_nodes: + input: "raw/egfr-prizes.txt" + output: "preprocessed/uniprot/input-nodes.txt" + shell: "uv run scripts/process_input_nodes.py" + +rule map_ensembl: + input: + "raw/HUMAN_9606_idmapping_selected.tsv", + "preprocessed/ensp/interactome.tsv", + + "preprocessed/uniprot/input-nodes.txt", + "preprocessed/uniprot/gold-standard-nodes.txt", + output: + "preprocessed/ensp/input-nodes.txt", + "preprocessed/ensp/gold-standard-nodes.txt" + shell: "uv run scripts/map_ensembl.py" + +rule normalize_ensp: + input: + gold_standard="preprocessed/ensp/gold-standard-nodes.txt", + input_nodes="preprocessed/ensp/input-nodes.txt", + interactome="preprocessed/ensp/interactome.tsv", + output: + gold_standard="processed/ensp/gold-standard-nodes.txt", + input_nodes="processed/ensp/input-nodes.txt", + interactome="processed/ensp/interactome.tsv", + shell: + "uv run ../../tools/normalize/cli.py --interactome={input.interactome} --interactome-output={output.interactome} " \ + + "--gold-standard={input.gold_standard} --gold-standard-output={output.gold_standard} " \ + + "--input-nodes={input.input_nodes} --input-nodes-output={output.input_nodes}" diff --git a/datasets/egfr/scripts/map_ensembl.py b/datasets/egfr/scripts/map_ensembl.py new file mode 100644 index 0000000..c12caba --- /dev/null +++ b/datasets/egfr/scripts/map_ensembl.py @@ -0,0 +1,36 @@ +import pandas +from pathlib import Path +from tools.mapping.ensembl_uniprot import idmapping_uniprot_mapping + +egfr_directory = Path(__file__).parent.resolve() / ".." + + +def main(): + # Re-read the uniprot nodes from `process_gold_standard.py` + gold_standard_nodes = (egfr_directory / "preprocessed" / "uniprot" / "gold-standard-nodes.txt").read_text().splitlines() + # and the input nodes from `process_input_nodes.py` + prizes = pandas.read_csv(egfr_directory / "preprocessed" / "uniprot" / "input-nodes.txt", sep="\t") + + # We grab our UniProt <-> ENSP mapping + idmapping_df = idmapping_uniprot_mapping(egfr_directory / "raw" / "HUMAN_9606_idmapping_selected.tsv") + + # Then map the gold standard nodes + idmapped_gold_standard_nodes_df = pandas.DataFrame(gold_standard_nodes, columns=["UniProtKB-ID"]).merge( + idmapping_df, on="UniProtKB-ID", how="left" + ) + idmapped_gold_standard_nodes_df = idmapped_gold_standard_nodes_df.drop(columns=["UniProtKB-ID", "UniProtKB-AC", "Ensembl"]) + idmapped_gold_standard_nodes_df = idmapped_gold_standard_nodes_df[~idmapped_gold_standard_nodes_df["Ensembl_PRO"].isna()] + gold_standard_nodes = idmapped_gold_standard_nodes_df["Ensembl_PRO"].astype(str).to_list() + (egfr_directory / "preprocessed" / "ensp" / "gold-standard-nodes.txt").write_text("\n".join(gold_standard_nodes)) + + # Then map the input nodes + idmapped_input_nodes_df = prizes.merge(idmapping_df, left_on="NODEID", right_on="UniProtKB-ID", how="inner") + idmapped_input_nodes_df = idmapped_input_nodes_df.drop(columns=["UniProtKB-ID", "UniProtKB-AC", "Ensembl", "NODEID"]) + idmapped_input_nodes_df = idmapped_input_nodes_df[~idmapped_input_nodes_df["Ensembl_PRO"].isna()] + idmapped_input_nodes_df = idmapped_input_nodes_df.rename(columns={"Ensembl_PRO": "NODEID"}) + idmapped_input_nodes_df = idmapped_input_nodes_df[["NODEID", "prize", "active", "dummy", "source"]] + idmapped_input_nodes_df.to_csv(egfr_directory / "preprocessed" / "ensp" / "input-nodes.txt", sep="\t", index=False) + + +if __name__ == "__main__": + main() diff --git a/datasets/egfr/scripts/process_gold_standard.py b/datasets/egfr/scripts/process_gold_standard.py new file mode 100644 index 0000000..86205e9 --- /dev/null +++ b/datasets/egfr/scripts/process_gold_standard.py @@ -0,0 +1,16 @@ +from pathlib import Path + +egfr_directory = Path(__file__).parent.resolve() / ".." + + +def main(): + # We remove all PSUEDONODES (and any duplicates via `set`) + nodes = (egfr_directory / "raw" / "eight-egfr-reference-all.txt").read_text().splitlines() + nodes = list(set([node for node in nodes if not node.endswith("_PSEUDONODE")])) + + (egfr_directory / "preprocessed").mkdir(exist_ok=True) + (egfr_directory / "preprocessed" / "uniprot" / "gold-standard-nodes.txt").write_text("\n".join(nodes)) + + +if __name__ == "__main__": + main() diff --git a/datasets/egfr/scripts/process_input_nodes.py b/datasets/egfr/scripts/process_input_nodes.py new file mode 100644 index 0000000..dfd95f3 --- /dev/null +++ b/datasets/egfr/scripts/process_input_nodes.py @@ -0,0 +1,29 @@ +import pandas +from pathlib import Path + +egfr_directory = Path(__file__).parent.resolve() / ".." + +SOURCE = "EGF_HUMAN" + +# TODO: Identify a programmatic way to generate this magic value. It has the constraints of: +# - PRIZE_VALUE > prizes.loc[:, "prize"].max() +# - not too big +# and this value experimentally was one that worked, but there are others that may work and would admit a more precise description +PRIZE_VALUE = 10 + +def main(): + prizes = pandas.read_csv(egfr_directory / "raw" / "egfr-prizes.txt", sep="\t", header=None, names=["NODEID", "prize"]) + prizes = prizes.loc[~prizes["NODEID"].str.endswith("_PSEUDONODE")] + prizes["target"] = "True" + # `SOURCE` is designated as the source node and assigned the highest score to anchor reconstruction at the perturbation. + prizes = pandas.concat( + [prizes, pandas.DataFrame({"NODEID": [SOURCE], "prize": [PRIZE_VALUE], "dummy": ["True"], "source": ["True"]})], + ignore_index=True, + ) + prizes["active"] = "True" + + prizes.to_csv(egfr_directory / "preprocessed" / "uniprot" / "input-nodes.txt", index=False, sep="\t") + + +if __name__ == "__main__": + main() diff --git a/datasets/egfr/scripts/process_interactome.py b/datasets/egfr/scripts/process_interactome.py new file mode 100644 index 0000000..d61d212 --- /dev/null +++ b/datasets/egfr/scripts/process_interactome.py @@ -0,0 +1,30 @@ +from pathlib import Path +import pandas + +from tools.normalize.interactome import deduplicate_edges + +egfr_directory = Path(__file__).parent.resolve() / ".." + + +def main(): + + # TODO: update to be the physical links interactome + interactome_df = pandas.read_csv(egfr_directory / "raw" / "9606.protein.links.full.txt", sep=" ") + # Rename the columns both to stylistically keep it in-line with SPRAS and functionally for `deduplicate_edges`. + interactome_df = interactome_df.rename(columns={"protein1": "Interactor1", "protein2": "Interactor2", "combined_score": "Weight"}) + interactome_df["Interactor1"] = interactome_df["Interactor1"].astype(str).str.removeprefix("9606.") + interactome_df["Interactor2"] = interactome_df["Interactor2"].astype(str).str.removeprefix("9606.") + # STRING provides two links (interactome) files: links and links.full. For convenience in other datasets, + # we only download and use links.full, even though links would suffice. Due to that, we restrict to the + # subset of headers in links before saving the interactome. + interactome_df = interactome_df[["Interactor1", "Interactor2", "Weight"]] + interactome_df["Direction"] = "U" + + # We normalize the interactome (any final post-processing steps wanted/needed by SPRAS). + (egfr_directory / "preprocessed").mkdir(exist_ok=True) + interactome_df, _ = deduplicate_edges(interactome_df) + interactome_df.to_csv(egfr_directory / "preprocessed" / "ensp" / "interactome.tsv", index=False, header=False, sep="\t") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..713bcb8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[project] +name = "spras-benchmarking" +version = "0.1.0" +description = "SPRAS Benchmarking Repository" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "frozendict>=2.4.7", + "gdown>=5.2.0", + "jsonc-parser>=1.1.5", + "loguru>=0.7.3", + "more-itertools>=10.7.0", + "networkx>=3.6.1", + "openpyxl>=3.1.5", + "pandas>=2.3.0", + "paxtools>=6.0.0.2", + "pydantic>=2.12.5", + "requests>=2.32.5", +] + +[dependency-groups] +dev = [ + "pre-commit>=4.2.0", + "pytest>=9.0.3", + "snakemake==9.9.0", +] + +[tool.ruff] +line-length = 150 +exclude = ["spras"] + +[tool.ruff.lint] +extend-ignore = ["E712"] + +[tool.uv] +package = true + +[tool.setuptools.packages.find] +namespaces = false +where = ["./"] +include = ["cache", "tools"] +exclude = ["spras*"] + +[build-system] +requires = ["setuptools", "wheel", "pip"] +build-backend = "setuptools.build_meta" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..9d59e3f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --ignore=spras diff --git a/run_snakemake.sh b/run_snakemake.sh new file mode 100755 index 0000000..3068da2 --- /dev/null +++ b/run_snakemake.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Snakemake does not support importing and orchestrating multiple Snakefiles +# in a way that respects the origin directory. +# Instead, we provide a shell script. +# We use some error-catching boilerplate from https://sharats.me/posts/shell-script-best-practices/. + +# Strict settings +set -o errexit +set -o nounset + +# Forcibly use the CWD +cd "$(dirname "$0")" + +main() { + uv run snakemake --cores 1 -d datasets/depmap -s datasets/depmap/Snakefile + uv run snakemake --cores 1 -d datasets/egfr -s datasets/egfr/Snakefile +} + +main "$@" diff --git a/spras b/spras new file mode 160000 index 0000000..479842d --- /dev/null +++ b/spras @@ -0,0 +1 @@ +Subproject commit 479842d6954f5df448f628259588c5a038e8efef diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cache/__init__.py b/tests/cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cache/test_directory_redirects.py b/tests/cache/test_directory_redirects.py new file mode 100644 index 0000000..459473e --- /dev/null +++ b/tests/cache/test_directory_redirects.py @@ -0,0 +1,11 @@ +from frozendict import frozendict +from cache.directory import CacheDirectory, CacheItem, get_cache_item + +test_item = CacheItem(name="Test Item", cached="https://drive.google.com/uc?id=", unpinned="", pinned="") + +custom_directory: CacheDirectory = frozendict({"test": {"alias": "v1", "v1": test_item}}) + + +def test_directory_fetch(): + assert get_cache_item(("test", "v1"), custom_directory=custom_directory) == test_item + assert get_cache_item(("test", "alias"), custom_directory=custom_directory) == test_item diff --git a/tests/tools/__init__.py b/tests/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/tools/test_deduplicate_edges.py b/tests/tools/test_deduplicate_edges.py new file mode 100644 index 0000000..47f9e5f --- /dev/null +++ b/tests/tools/test_deduplicate_edges.py @@ -0,0 +1,15 @@ +import pandas +import numpy +from tools.normalize.interactome import deduplicate_edges + + +def test_deduplicate_edges(): + df = pandas.DataFrame( + {"Interactor1": ["a", "b", "e", "d"], "Interactor2": ["b", "a", "d", "e"], "Weight": [1, 2, 3, 4], "Direction": ["U", "U", "D", "D"]} + ) + + assert len(df.index) == 4 + + deduplicated_df, _ = deduplicate_edges(df) + assert len(deduplicated_df.index) == 3 + assert list(deduplicated_df.iloc[0]) == ["d", "e", numpy.int64(4), "D"] diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..b977d5c --- /dev/null +++ b/tools/README.md @@ -0,0 +1,4 @@ +# Dataset Processing Tools + +This includes common tools for doing dataset processing, which take in SPRAS-compatible file formats. +See `__init__.py` in the various subfolders of `tools` for more info. diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/mapping/__init__.py b/tools/mapping/__init__.py new file mode 100644 index 0000000..8881378 --- /dev/null +++ b/tools/mapping/__init__.py @@ -0,0 +1,3 @@ +""" +General mapping utilities between various namespaces. This folder contains such utilities. +""" diff --git a/tools/mapping/ensembl_uniprot.py b/tools/mapping/ensembl_uniprot.py new file mode 100644 index 0000000..12ecbd8 --- /dev/null +++ b/tools/mapping/ensembl_uniprot.py @@ -0,0 +1,57 @@ +import pandas +import os + +""" +Utilities for mapping Ensembl and UniProt. + +For example, +```py +idmapping_uniprot_ensembl = idmapping_uniprot_mapping(path / "HUMAN_9606_idmapping_selected.tsv") +``` + +then you can use the `idmapping_as_ensg_uniprot_mapping` or `idmapping_as_ensp_uniprot_mapping` to restrict the mapping to specifically +ENSG or ENSP. +""" + + +def handle_ensembl_list(idmapping_df: pandas.DataFrame, column_name: str) -> pandas.DataFrame: + idmapping_df = idmapping_df[idmapping_df[column_name].notnull()] + # Handle our ;-delimited list + idmapping_df.loc[:, column_name] = idmapping_df[column_name].str.split("; ") + idmapping_df = idmapping_df.explode(column_name) + # Drop isoforms + idmapping_df.loc[:, column_name] = idmapping_df[column_name].str.split(".").str[0] + idmapping_df = idmapping_df.reset_index(drop=True) + return idmapping_df + + +def idmapping_uniprot_mapping(path: str | os.PathLike) -> pandas.DataFrame: + """ + Gets the UniProt mapping file (`*_idmapping_selected`) as a dataframe with columns + UniProtKB-AC: UniProt Accession Numbers + UniProtKB-ID + Ensembl: ENSG + Ensembl_PRO: ENSG (Ensembl Protein IDs) + """ + # The very powerful UniProt-provided mapping file: its Ensembl mappings are a semicolon-delimited list of Emsembl IDs containing + # attached isoforms (and not all UniProtKB-AC identifiers have those!) so we'll need to do some extra post-processing. + # This is `*_idmapping_selected`. + idmapping_selected_df = pandas.read_csv( + path, + header=None, + # See directory.py for the README associated with this mapping file. + usecols=[0, 1, 18, 20], + names=["UniProtKB-AC", "UniProtKB-ID", "Ensembl", "Ensembl_PRO"], + sep="\t", + ) + idmapping_selected_df = handle_ensembl_list(idmapping_selected_df, "Ensembl") + idmapping_selected_df = handle_ensembl_list(idmapping_selected_df, "Ensembl_PRO") + return idmapping_selected_df + + +def idmapping_as_ensg_uniprot_mapping(uniprot_mapping: pandas.DataFrame): + return uniprot_mapping.drop(columns=["Ensembl_PRO"]) + + +def idmapping_as_ensp_uniprot_mapping(uniprot_mapping: pandas.DataFrame): + return uniprot_mapping.drop(columns=["Ensembl"]).rename(columns={"Ensembl_PRO": "Ensembl"}) diff --git a/tools/normalize/__init__.py b/tools/normalize/__init__.py new file mode 100644 index 0000000..b997eaa --- /dev/null +++ b/tools/normalize/__init__.py @@ -0,0 +1 @@ +# The entrypoint file for this is in normalize/cli.py diff --git a/tools/normalize/cli.py b/tools/normalize/cli.py new file mode 100644 index 0000000..6a68d3f --- /dev/null +++ b/tools/normalize/cli.py @@ -0,0 +1,60 @@ +""" +Allows for easy SPRAS post-data processing for any dataset via a CLI callable in Snakemake. +Specifically, this: +- Trims other input files that aren't the interactome. +- Removes duplicate edges from the interactome. + +See EGFR for a Snakemake dataset-based example usage of this. +""" + +import argparse +from pathlib import Path +import pandas + +from tools.normalize.interactome import deduplicate_edges +from tools.normalize.trim_input_nodes import trim_input_nodes_file +from tools.normalize.trim_list import trim_node_list_file + + +def argparser(): + # The language and setup of `deciding_parser` and `parser` is from + # https://stackoverflow.com/a/70716254/7589775. This allows us to make + # the other outputs conditionally required. + deciding_parser = argparse.ArgumentParser(add_help=False) + deciding_parser.add_argument("--interactome", help="The input interactome file", required=True) + deciding_parser.add_argument("--interactome-output", help="Where to output the new interactome file.", required=True) + deciding_parser.add_argument("--input-nodes", help="The input nodes file to trim against.") + deciding_parser.add_argument("--gold-standard", help="The gold standard file to trim against.") + + deciding_args, _ = deciding_parser.parse_known_args() + + parser = argparse.ArgumentParser(parents=[deciding_parser]) + parser.add_argument("--input-nodes-output", help="Where to output the new input nodes file.", required=deciding_args.input_nodes) + parser.add_argument("--gold-standard-output", help="Where to output the new gold standard file.", required=deciding_args.gold_standard) + + return parser + + +def main(): + args = argparser().parse_args() + + print("Deduplicating interactome...") + interactome_df = pandas.read_csv(args.interactome, sep="\t", header=None, names=["Interactor1", "Interactor2", "Weight", "Direction"]) + deduplicated_interactome, _ = deduplicate_edges(interactome_df) + Path(args.interactome_output).parent.mkdir(parents=True, exist_ok=True) + deduplicated_interactome.to_csv(args.interactome_output, sep="\t", index=False, header=False) + + if args.input_nodes: + print("Trimming input nodes...") + new_input_nodes = trim_input_nodes_file(interactome_df, pandas.read_csv(args.input_nodes, sep="\t")) + Path(args.input_nodes_output).parent.mkdir(parents=True, exist_ok=True) + new_input_nodes.to_csv(args.input_nodes_output, sep="\t", index=False) + + if args.gold_standard: + print("Trimming gold standard nodes list...") + Path(args.gold_standard_output).parent.mkdir(parents=True, exist_ok=True) + trim_node_list_file(interactome_df, Path(args.gold_standard), Path(args.gold_standard_output)) + + +if __name__ == "__main__": + main() diff --git a/tools/normalize/interactome.py b/tools/normalize/interactome.py new file mode 100644 index 0000000..e20e7f9 --- /dev/null +++ b/tools/normalize/interactome.py @@ -0,0 +1,47 @@ +import pandas + + +def deduplicate_edges(interactome_df: pandas.DataFrame) -> tuple[pandas.DataFrame, bool]: + """ + Removes duplicate edges from the input `DataFrame` as an interactome. + - For undirected edges, the node pair is sorted (e.g., "B-A" becomes "A-B") before removing duplicates. + - Directed edges are deduplicated by exact (Interactor1, Interactor2) match; "A->B" and "B->A" remain distinct. + - When duplicates are found, the row with the highest Weight is retained. + + @param interactome_df: A DataFrame from a raw file pathway. + @return pd.DataFrame: A DataFrame with duplicate edges removed. + @return bool: True if duplicate edges were found and removed, False otherwise. + """ + # for undirected edges, sort node pairs so that Node1 is always the lesser of the two + undirected_mask = interactome_df["Direction"] == "U" + + # computes the minimum and maximum node (sorted order) for each row under the mask + min_nodes = interactome_df.loc[undirected_mask, ["Interactor1", "Interactor2"]].min(axis=1) + max_nodes = interactome_df.loc[undirected_mask, ["Interactor1", "Interactor2"]].max(axis=1) + + # assigns the sorted Node1 and Node2 back into the df + interactome_df.loc[undirected_mask, "Interactor1"] = min_nodes + interactome_df.loc[undirected_mask, "Interactor2"] = max_nodes + + # sort by Weight descending so keep="first" retains the highest-weight duplicate + interactome_df.sort_values("Weight", ascending=False, inplace=True) + + # remove duplicated edges + unique_edges_df = interactome_df.drop_duplicates(subset=["Interactor1", "Interactor2", "Direction"], keep="first", ignore_index=True) + + # return True if duplicates edges are removed + duplicates_removed = len(unique_edges_df) < len(interactome_df) + + return unique_edges_df, duplicates_removed + + +def get_interactome_nodes(interactome_df: pandas.DataFrame) -> set[str]: + """ + Gets all nodes associated with an interactome. + + @param interactome_df: The pandas dataframe with Interactor1 and Interactor2 as columns. + @return the list of nodes in Interactor1 and Interactor2. + + NOTE: This isn't guaranteed to be order stable, not for any externally-required reason. + """ + return set(list(interactome_df["Interactor1"])).union(list(interactome_df["Interactor2"])) diff --git a/tools/normalize/trim_input_nodes.py b/tools/normalize/trim_input_nodes.py new file mode 100644 index 0000000..a076002 --- /dev/null +++ b/tools/normalize/trim_input_nodes.py @@ -0,0 +1,14 @@ +import pandas + + +def trim_input_nodes_file(interactome_df: pandas.DataFrame, input_nodes_df: pandas.DataFrame) -> pandas.DataFrame: + """ + Keep input nodes that appear anywhere in the interactome. + + Nodes absent from the interactome cannot contribute to pathway reconstruction. + + Both inputs must be in SPRAS format. The interactome is expected to have the + columns Interactor1, Interactor2, Weight, Direction. + """ + interactome_nodes = set(interactome_df["Interactor1"]).union(interactome_df["Interactor2"]) + return input_nodes_df.loc[input_nodes_df["NODEID"].isin(interactome_nodes), :] \ No newline at end of file diff --git a/tools/normalize/trim_list.py b/tools/normalize/trim_list.py new file mode 100644 index 0000000..2386746 --- /dev/null +++ b/tools/normalize/trim_list.py @@ -0,0 +1,24 @@ +import pandas +from pathlib import Path +from tools.normalize.interactome import get_interactome_nodes + + +def trim_node_list(interactome_nodes: list[str], nodes_to_trim: list[str]) -> list[str]: + """ + Trims a node list with the desired interactome. The actual method you may want to use is in + `trim_node_list_file`. + """ + return list(set(nodes_to_trim).intersection(interactome_nodes)) + + +def trim_node_list_file(interactome_df: pandas.DataFrame, node_list: Path, output: Path): + """ + Trims a node list file with the desired interactome. + """ + return output.write_text( + "\n".join( + trim_node_list( + list(get_interactome_nodes(interactome_df)), [line.strip() for line in node_list.read_text().splitlines() if line.strip() != ""] + ) + ) + ) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..dc79469 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1223 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "argparse-dataclass" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/ff/a2e4e328075ddef2ac3c9431eb12247e4ba707a70324894f1e6b4f43c286/argparse_dataclass-2.0.0.tar.gz", hash = "sha256:09ab641c914a2f12882337b9c3e5086196dbf2ee6bf0ef67895c74002cc9297f", size = 6395, upload-time = "2023-06-11T20:32:54.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/66/e6c0a808950ba5a4042e2fcedd577fc7401536c7db063de4d7c36be06f84/argparse_dataclass-2.0.0-py3-none-any.whl", hash = "sha256:3ffc8852a88d9d98d1364b4441a712491320afb91fb56049afd8a51d74bb52d2", size = 8762, upload-time = "2023-06-11T20:32:52.724Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[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 = "conda-inject" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/a8/8dc86113c65c949cc72d651461d6e4c544b3302a85ed14a5298829e6a419/conda_inject-1.3.2.tar.gz", hash = "sha256:0b8cde8c47998c118d8ff285a04977a3abcf734caf579c520fca469df1cd0aac", size = 3635, upload-time = "2024-05-27T12:20:58.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4c/fc30b69fb4062aee57e3ab7ff493647c4220144908f0839c619f912045bf/conda_inject-1.3.2-py3-none-any.whl", hash = "sha256:6e641b408980c2814e3e527008c30749117909a21ff47392f07ef807da93a564", size = 4133, upload-time = "2024-05-27T12:20:57.332Z" }, +] + +[[package]] +name = "configargparse" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/4d/6c9ef746dfcc2a32e26f3860bb4a011c008c392b83eabdfb598d1a8bbe5d/configargparse-1.7.1.tar.gz", hash = "sha256:79c2ddae836a1e5914b71d58e4b9adbd9f7779d4e6351a637b7d2d9b6c46d3d9", size = 43958, upload-time = "2025-05-23T14:26:17.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" }, +] + +[[package]] +name = "connection-pool" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/df/c9b4e25dce00f6349fd28aadba7b6c3f7431cc8bd4308a158fbe57b6a22e/connection_pool-0.0.3.tar.gz", hash = "sha256:bf429e7aef65921c69b4ed48f3d48d3eac1383b05d2df91884705842d974d0dc", size = 3795, upload-time = "2020-09-17T02:48:28.824Z" } + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "docutils" +version = "0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/86/5b41c32ecedcfdb4c77b28b6cb14234f252075f8cdb254531727a35547dd/docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f", size = 2277984, upload-time = "2025-07-29T15:20:31.06Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/57/8db39bc5f98f042e0153b1de9fb88e1a409a33cda4dd7f723c2ed71e01f6/docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e", size = 630709, upload-time = "2025-07-29T15:20:28.335Z" }, +] + +[[package]] +name = "dpath" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/ce/e1fd64d36e4a5717bd5e6b2ad188f5eaa2e902fde871ea73a79875793fc9/dpath-2.2.0.tar.gz", hash = "sha256:34f7e630dc55ea3f219e555726f5da4b4b25f2200319c8e6902c394258dd6a3e", size = 28266, upload-time = "2024-06-12T22:08:03.686Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/d1/8952806fbf9583004ab479d8f58a9496c3d35f6b6009ddd458bdd9978eaf/dpath-2.2.0-py3-none-any.whl", hash = "sha256:b330a375ded0a0d2ed404440f6c6a715deae5313af40bbb01c8a41d891900576", size = 17618, upload-time = "2024-06-12T22:08:01.881Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "frozendict" +version = "2.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/b2/2a3d1374b7780999d3184e171e25439a8358c47b481f68be883c14086b4c/frozendict-2.4.7.tar.gz", hash = "sha256:e478fb2a1391a56c8a6e10cc97c4a9002b410ecd1ac28c18d780661762e271bd", size = 317082, upload-time = "2025-11-11T22:40:14.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/74/f94141b38a51a553efef7f510fc213894161ae49b88bffd037f8d2a7cb2f/frozendict-2.4.7-py3-none-any.whl", hash = "sha256:972af65924ea25cf5b4d9326d549e69a9a4918d8a76a9d3a7cd174d98b237550", size = 16264, upload-time = "2025-11-11T22:40:12.836Z" }, +] + +[[package]] +name = "gdown" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "filelock" }, + { name = "requests", extra = ["socks"] }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/6a/37e6b70c5bda3161e40265861e63b64a86bfc6ca6a8f1c35328a675c84fd/gdown-5.2.0.tar.gz", hash = "sha256:2145165062d85520a3cd98b356c9ed522c5e7984d408535409fd46f94defc787", size = 284647, upload-time = "2024-05-12T06:45:12.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/70/e07c381e6488a77094f04c85c9caf1c8008cdc30778f7019bc52e5285ef0/gdown-5.2.0-py3-none-any.whl", hash = "sha256:33083832d82b1101bdd0e9df3edd0fbc0e1c5f14c9d8c38d2a35bf1683b526d6", size = 18235, upload-time = "2024-05-12T06:45:10.017Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "identify" +version = "2.6.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/c4/62963f25a678f6a050fb0505a65e9e726996171e6dbe1547f79619eefb15/identify-2.6.14.tar.gz", hash = "sha256:663494103b4f717cb26921c52f8751363dc89db64364cd836a9bf1535f53cd6a", size = 99283, upload-time = "2025-09-06T19:30:52.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ae/2ad30f4652712c82f1c23423d79136fbce338932ad166d70c1efb86a5998/identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e", size = 99172, upload-time = "2025-09-06T19:30:51.759Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "immutables" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/41/0ccaa6ef9943c0609ec5aa663a3b3e681c1712c1007147b84590cec706a0/immutables-0.21.tar.gz", hash = "sha256:b55ffaf0449790242feb4c56ab799ea7af92801a0a43f9e2f4f8af2ab24dfc4a", size = 89008, upload-time = "2024-10-10T00:55:01.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/db/60da6f5a3c3f64e0b3940c4ad86e1971d9d2eb8b13a179c26eda5ec6a298/immutables-0.21-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:79674b51aa8dd983f9ac55f7f67b433b1df84a6b4f28ab860588389a5659485b", size = 31922, upload-time = "2024-10-10T00:54:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/5420f1d16a652024fcccc9c07d46d4157fcaf33ff37c82412c83fc16ef36/immutables-0.21-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93c8350f8f7d0d9693f708229d9d0578e6f3b785ce6da4bced1da97137aacfad", size = 31552, upload-time = "2024-10-10T00:54:30.282Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d0/a5fb7c164ddb298ec37537e618b70dfa30c7cae9fac01de374c36489cbc9/immutables-0.21-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:583d2a63e444ce1538cc2bda56ae1f4a1a11473dbc0377c82b516bc7eec3b81e", size = 104334, upload-time = "2024-10-10T00:54:31.284Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a5/5fda0ee4a261a85124011ac0750fec678f00e1b2d4a5502b149a3b4d86d9/immutables-0.21-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b274a52da9b106db55eceb93fc1aea858c4e6f4740189e3548e38613eafc2021", size = 104898, upload-time = "2024-10-10T00:54:32.295Z" }, + { url = "https://files.pythonhosted.org/packages/93/fa/d46bfe92f2c66d35916344176ff87fa839aac9c16849652947e722b7a15f/immutables-0.21-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:338bede057250b33716a3e4892e15df0bf5a5ddbf1d67ead996b3e680b49ef9e", size = 99966, upload-time = "2024-10-10T00:54:34.046Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f5/2a19e2e095f7a39d8d77dcc10669734d2d99773ce00c99bdcfeeb7d714e6/immutables-0.21-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8781c89583b68f604cf30f0978b722165824c3075888639fde771bf1a3e12dc0", size = 101773, upload-time = "2024-10-10T00:54:35.851Z" }, + { url = "https://files.pythonhosted.org/packages/86/80/5b6ee53f836cf2067ced997efbf2ce20890627f150c3089ea50cf607e783/immutables-0.21-cp313-cp313-win32.whl", hash = "sha256:e97ea83befad873712f283c0cccd630f70cba753e207b4868af28d5b85e9dc54", size = 30988, upload-time = "2024-10-10T00:54:37.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/07/f623e6da78368fc0b1772f4877afbf60f34c4cc93f1a8f1006507afa21ec/immutables-0.21-cp313-cp313-win_amd64.whl", hash = "sha256:cfcb23bd898f5a4ef88692b42c51f52ca7373a35ba4dcc215060a668639eb5da", size = 35147, upload-time = "2024-10-10T00:54:38.558Z" }, +] + +[[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 = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonc-parser" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/85/bec7d88eca76c6d014d8efc17b0a9babb0810213e974bff3b7928158216b/jsonc-parser-1.1.5.tar.gz", hash = "sha256:7126d17725b0413cd40af4297d9f6412c4181a62135e4c41cdf8f6a82c5936e6", size = 4460, upload-time = "2021-05-23T19:11:05.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/be/7d27877578c43decdc38a5fac0c7aecde2e0d2f7b50869131ef9bfd1d36e/jsonc_parser-1.1.5-py3-none-any.whl", hash = "sha256:abd1db76a4c6d1733ec7bb5340a89c49cbc878a181a1e7947ee6719eedf2c6cc", size = 5671, upload-time = "2021-05-23T19:11:04.556Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/8e/0e90233ac205ad182bd6b422532695d2b9414944a280488105d598c70023/pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb", size = 4488684, upload-time = "2025-08-21T10:28:29.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e", size = 11531061, upload-time = "2025-08-21T10:27:34.647Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9", size = 10668666, upload-time = "2025-08-21T10:27:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/f775ba76ecfb3424d7f5862620841cf0edb592e9abd2d2a5387d305fe7a8/pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a", size = 11332835, upload-time = "2025-08-21T10:27:40.188Z" }, + { url = "https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b", size = 12057211, upload-time = "2025-08-21T10:27:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9d/2df913f14b2deb9c748975fdb2491da1a78773debb25abbc7cbc67c6b549/pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6", size = 12749277, upload-time = "2025-08-21T10:27:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/da1a2417026bd14d98c236dba88e39837182459d29dcfcea510b2ac9e8a1/pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a", size = 13415256, upload-time = "2025-08-21T10:27:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b", size = 10982579, upload-time = "2025-08-21T10:28:08.435Z" }, + { url = "https://files.pythonhosted.org/packages/f3/98/8df69c4097a6719e357dc249bf437b8efbde808038268e584421696cbddf/pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57", size = 12028163, upload-time = "2025-08-21T10:27:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/0e/23/f95cbcbea319f349e10ff90db488b905c6883f03cbabd34f6b03cbc3c044/pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2", size = 11391860, upload-time = "2025-08-21T10:27:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1b/6a984e98c4abee22058aa75bfb8eb90dce58cf8d7296f8bc56c14bc330b0/pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9", size = 11309830, upload-time = "2025-08-21T10:27:56.957Z" }, + { url = "https://files.pythonhosted.org/packages/15/d5/f0486090eb18dd8710bf60afeaf638ba6817047c0c8ae5c6a25598665609/pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2", size = 11883216, upload-time = "2025-08-21T10:27:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/10/86/692050c119696da19e20245bbd650d8dfca6ceb577da027c3a73c62a047e/pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012", size = 12699743, upload-time = "2025-08-21T10:28:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" }, +] + +[[package]] +name = "paxtools" +version = "6.0.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7e/bcaa11908406cdffb5eeae6797244e99d4d5111e0a4e22a7eea13607af52/paxtools-6.0.0.2.tar.gz", hash = "sha256:0a313311fa313fc222c53874dfa5641b86cbedc49a2fb5da3b120949f7b84377", size = 13691523, upload-time = "2026-02-25T08:23:37.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/e7/369d85283551f0770e16ce7054a62b4eb92107d72d2108fc563856323a67/paxtools-6.0.0.2-py3-none-any.whl", hash = "sha256:2301d374f0f5a7a72c7dcff5528ab3b0cd524c946a7246984e4221c8d36d0430", size = 13695547, upload-time = "2026-02-25T08:23:34.992Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "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 = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "pulp" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4f/11cfa283228b5f259bcfc913f731f7c6f68748d26711594e14cf2cb5e39a/pulp-3.2.2.tar.gz", hash = "sha256:389a6ff1dc34ec4b093f34f7a9fa3553743ff0ea99b2a423e9f0dd16940f63d2", size = 16299367, upload-time = "2025-07-29T11:42:04.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/8d/a6a9d58c929a869f7f1b99b3d37b3f14ef63e2826eef581416338d686c3f/pulp-3.2.2-py3-none-any.whl", hash = "sha256:d3ca5ff11a28b3e7b2508a992d7e51f3533471d89305f0560b5fe3b6cc821043", size = 16385354, upload-time = "2025-07-29T11:42:01.829Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +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 = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "reretry" +version = "0.11.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/1d/25d562a62b7471616bccd7c15a7533062eb383927e68667bf331db990415/reretry-0.11.8.tar.gz", hash = "sha256:f2791fcebe512ea2f1d153a2874778523a8064860b591cd90afc21a8bed432e3", size = 4836, upload-time = "2022-12-18T11:08:50.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/11/e295e07d4ae500144177f875a8de11daa4d86b8246ab41c76a98ce9280ca/reretry-0.11.8-py2.py3-none-any.whl", hash = "sha256:5ec1084cd9644271ee386d34cd5dd24bdb3e91d55961b076d1a31d585ad68a79", size = 5609, upload-time = "2022-12-18T11:08:49.1Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, +] + +[[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 = "smart-open" +version = "7.3.0.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/2b/5e7234c68ed5bc872ad6ae77b8a421c2ed70dcb1190b44dc1abdeed5e347/smart_open-7.3.0.post1.tar.gz", hash = "sha256:ce6a3d9bc1afbf6234ad13c010b77f8cd36d24636811e3c52c3b5160f5214d1e", size = 51557, upload-time = "2025-07-03T10:06:31.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/5b/a2a3d4514c64818925f4e886d39981f1926eeb5288a4549c6b3c17ed66bb/smart_open-7.3.0.post1-py3-none-any.whl", hash = "sha256:c73661a2c24bf045c1e04e08fffc585b59af023fe783d57896f590489db66fb4", size = 61946, upload-time = "2025-07-03T10:06:29.599Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "snakemake" +version = "9.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "conda-inject" }, + { name = "configargparse" }, + { name = "connection-pool" }, + { name = "docutils" }, + { name = "dpath" }, + { name = "gitpython" }, + { name = "humanfriendly" }, + { name = "immutables" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pulp" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, + { name = "reretry" }, + { name = "smart-open" }, + { name = "snakemake-interface-common" }, + { name = "snakemake-interface-executor-plugins" }, + { name = "snakemake-interface-logger-plugins" }, + { name = "snakemake-interface-report-plugins" }, + { name = "snakemake-interface-storage-plugins" }, + { name = "tabulate" }, + { name = "throttler" }, + { name = "wrapt" }, + { name = "yte" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/8d/bb5c02a536ccb1d15f03a7afc2a74629458f48985fe2826d4738151eac00/snakemake-9.9.0.tar.gz", hash = "sha256:38b5e1a39d4f6eee361c9e30da1de5ec629ff67581e6ac3497e415d92f02961d", size = 5981228, upload-time = "2025-07-29T16:11:37.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/a4/1cabb89926ce72279b00a417c0a3a5d7fc8da66f6112923a3e14aaf82c7e/snakemake-9.9.0-py3-none-any.whl", hash = "sha256:fae92d7f4679bc32e349d9c92bba83c147a460a0af43fb63cd9712b32e024531", size = 1101715, upload-time = "2025-07-29T16:11:35.912Z" }, +] + +[[package]] +name = "snakemake-interface-common" +version = "1.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argparse-dataclass" }, + { name = "configargparse" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/d6/f28dab9c084d01ac8b916e835f35838ce801cdc7176c5ed59cf1b39b42c1/snakemake_interface_common-1.21.0.tar.gz", hash = "sha256:0b6f0ef2c1a19fa8c20d676f4e355b8ba7058e142640a1c3c36fd1b9e110ef53", size = 13760, upload-time = "2025-07-24T07:46:40.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/24/de0615747b9eebd2b5abf9f629120224338bac0a09a46ab86ec72d401645/snakemake_interface_common-1.21.0-py3-none-any.whl", hash = "sha256:844a23831ceab1069b859732a8f6d8151b75b89ad8ad4c58ab0dcfdea234b40a", size = 16723, upload-time = "2025-07-24T07:46:38.661Z" }, +] + +[[package]] +name = "snakemake-interface-executor-plugins" +version = "9.3.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argparse-dataclass" }, + { name = "snakemake-interface-common" }, + { name = "throttler" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/51/e62e14090393d6688e7d4026a574f0a9de14ffb678bc4c6993306fc3e62a/snakemake_interface_executor_plugins-9.3.9.tar.gz", hash = "sha256:988ab388d48522fac84107867ae3f3398312b93b55df6ed7b99afc225468ca26", size = 16530, upload-time = "2025-07-29T15:34:21.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/8b/fec4419acedfa5924549f40664cb2134f2ea5fae3d8a39df5e24035df06a/snakemake_interface_executor_plugins-9.3.9-py3-none-any.whl", hash = "sha256:bae310d5e258d5504731cca69d73051cd5ae1702d46fa66c03ef947be27fe09a", size = 22511, upload-time = "2025-07-29T15:34:20.472Z" }, +] + +[[package]] +name = "snakemake-interface-logger-plugins" +version = "1.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snakemake-interface-common" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/d4/3cb00ef9a65bdb7f533f42df04873f96e08b8ef39eaf33c69db2e6f87f8b/snakemake_interface_logger_plugins-1.2.4.tar.gz", hash = "sha256:09193b07c260b3efc88a75a0d33767820705f66e85c14d4f0d0e562b123c3c58", size = 11858, upload-time = "2025-07-24T13:50:58.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/c1/5af0e20e8284c890968af059868290e8c070f3676751abc524b3c4cd42a8/snakemake_interface_logger_plugins-1.2.4-py3-none-any.whl", hash = "sha256:659ddf3448024b90d2715ea82b9e4dd77ffae42615d2ba30479faaa2755a1b65", size = 10087, upload-time = "2025-07-24T13:50:56.82Z" }, +] + +[[package]] +name = "snakemake-interface-report-plugins" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "snakemake-interface-common" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/0f/b0986bacc1087d549e2ce5f5987de73980dc7041b7c518286f5035160d89/snakemake_interface_report_plugins-1.2.0.tar.gz", hash = "sha256:36cff4d50e7763ae0def0a7cf36d85e6c575d7bad1a3ade26b66c9b2b8831c02", size = 4251, upload-time = "2025-07-29T15:47:04.615Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/e0/fad8a90958288c47a26bd0b055301de6915df4bbc19cc1fd5eb5a8e29d4d/snakemake_interface_report_plugins-1.2.0-py3-none-any.whl", hash = "sha256:3f2fa6f49029f50f00b380f9a688a6d61b267f971b81faedff33188c0bf661f8", size = 7191, upload-time = "2025-07-29T15:47:03.781Z" }, +] + +[[package]] +name = "snakemake-interface-storage-plugins" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "reretry" }, + { name = "snakemake-interface-common" }, + { name = "throttler" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/2d/ecd72f48e1ed49b5cc6651f1ed71ce1fdf9c86de8c241b62c0c6cd33ac51/snakemake_interface_storage_plugins-4.2.2.tar.gz", hash = "sha256:a9da228c21252e60b72ddcec68cdf581f695a9db177053b5bd565e002dc729eb", size = 14263, upload-time = "2025-07-29T15:42:30.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/97/4389957c0db9628a7661f6562fa7248a2f3881067bc83752220a80a34f34/snakemake_interface_storage_plugins-4.2.2-py3-none-any.whl", hash = "sha256:44b422c7c3d2de378b4c874cc3d421c1137309074e238f649b63f9f45956e412", size = 17122, upload-time = "2025-07-29T15:42:28.976Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "spras-benchmarking" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "frozendict" }, + { name = "gdown" }, + { name = "jsonc-parser" }, + { name = "loguru" }, + { name = "more-itertools" }, + { name = "networkx" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "paxtools" }, + { name = "pydantic" }, + { name = "requests" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "snakemake" }, +] + +[package.metadata] +requires-dist = [ + { name = "frozendict", specifier = ">=2.4.7" }, + { name = "gdown", specifier = ">=5.2.0" }, + { name = "jsonc-parser", specifier = ">=1.1.5" }, + { name = "loguru", specifier = ">=0.7.3" }, + { name = "more-itertools", specifier = ">=10.7.0" }, + { name = "networkx", specifier = ">=3.6.1" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "pandas", specifier = ">=2.3.0" }, + { name = "paxtools", specifier = ">=6.0.0.2" }, + { name = "pydantic", specifier = ">=2.12.5" }, + { name = "requests", specifier = ">=2.32.5" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "snakemake", specifier = "==9.9.0" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "throttler" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/22/638451122136d5280bc477c8075ea448b9ebdfbd319f0f120edaecea2038/throttler-1.2.2.tar.gz", hash = "sha256:d54db406d98e1b54d18a9ba2b31ab9f093ac64a0a59d730c1cf7bb1cdfc94a58", size = 7970, upload-time = "2022-11-22T19:08:57.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/d4/36bf6010b184286000b2334622bfb3446a40c22c1d2a9776bff025cb0fe5/throttler-1.2.2-py3-none-any.whl", hash = "sha256:fc6ae612a2529e01110b32335af40375258b98e3b81232ec77cd07f51bf71392", size = 7609, upload-time = "2022-11-22T19:08:55.699Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "yte" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argparse-dataclass" }, + { name = "dpath" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/ac/eb7ef47a0d307737812f9d8734d288a429849d430049049370814ab814d9/yte-1.9.0.tar.gz", hash = "sha256:d259548aa46c5d5ff318f3ecb1732918d1ac3c6708798767ce3823192ce1ba21", size = 7404, upload-time = "2025-06-25T15:18:07.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/65/a961f5b88f12dc14e86715403dbeb39a69798d26ce5ddffc596cdeb4e2e5/yte-1.9.0-py3-none-any.whl", hash = "sha256:f7376ed048e7b1e96beb570bcb3b675b2bd54b5fb38890f663e95b6d9973a221", size = 9651, upload-time = "2025-06-25T15:18:07.209Z" }, +]