diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c98df58..4cba493 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ on: env: CARGO_TERM_COLOR: always - RELEASE_BINARIES: timsseek timsquery_cli timsquery_viewer + RELEASE_BINARIES: timsseek timsquery_cli timsquery_viewer speclib_build jobs: build-and-release: diff --git a/Cargo.lock b/Cargo.lock index 9c9c900..dbb9b1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,7 +284,7 @@ dependencies = [ [[package]] name = "array2d" -version = "0.27.0" +version = "0.28.0" dependencies = [ "serde", ] @@ -1205,9 +1205,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + [[package]] name = "bit-vec" version = "0.8.0" @@ -1275,6 +1281,17 @@ dependencies = [ "piper", ] +[[package]] +name = "bloomfilter" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c541c70a910b485670304fd420f0eab8f7bde68439db6a8d98819c3d2774d7e2" +dependencies = [ + "bit-vec 0.7.0", + "getrandom 0.2.17", + "siphasher", +] + [[package]] name = "bon" version = "3.9.1" @@ -1377,7 +1394,7 @@ dependencies = [ [[package]] name = "calibrt" -version = "0.27.0" +version = "0.28.0" dependencies = [ "array2d", "insta", @@ -1656,7 +1673,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -2114,6 +2131,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -2349,6 +2375,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2356,7 +2391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2370,6 +2405,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -3045,6 +3086,22 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -3063,9 +3120,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -3731,7 +3790,7 @@ dependencies = [ "bitflags 2.11.0", "block", "core-graphics-types 0.2.0", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -3739,7 +3798,7 @@ dependencies = [ [[package]] name = "micromzpaf" -version = "0.27.0" +version = "0.28.0" dependencies = [ "rustyms", "serde", @@ -3859,6 +3918,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -4357,12 +4433,50 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "openssl" +version = "0.10.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-sys" +version = "0.9.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.51" @@ -4705,7 +4819,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -5095,6 +5209,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-core", "futures-util", "h2", @@ -5103,9 +5218,12 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -5117,6 +5235,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -5570,6 +5689,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5803,6 +5931,26 @@ dependencies = [ "libm", ] +[[package]] +name = "speclib_build_cli" +version = "0.28.0" +dependencies = [ + "bloomfilter", + "clap", + "indicatif", + "micromzpaf", + "reqwest", + "rustyms", + "serde", + "serde_json", + "tempfile", + "timsseek", + "tokio", + "toml", + "tracing", + "tracing-subscriber", +] + [[package]] name = "spin" version = "0.9.8" @@ -5904,6 +6052,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -6047,7 +6216,7 @@ dependencies = [ [[package]] name = "timscentroid" -version = "0.27.0" +version = "0.28.0" dependencies = [ "arrow", "async-trait", @@ -6077,7 +6246,7 @@ dependencies = [ [[package]] name = "timsquery" -version = "0.27.0" +version = "0.28.0" dependencies = [ "array2d", "arrow", @@ -6100,7 +6269,7 @@ dependencies = [ [[package]] name = "timsquery_cli" -version = "0.27.0" +version = "0.28.0" dependencies = [ "clap", "half", @@ -6119,7 +6288,7 @@ dependencies = [ [[package]] name = "timsquery_pyo3" -version = "0.27.0" +version = "0.28.0" dependencies = [ "numpy", "pyo3", @@ -6130,7 +6299,7 @@ dependencies = [ [[package]] name = "timsquery_viewer" -version = "0.27.0" +version = "0.28.0" dependencies = [ "calibrt", "clap", @@ -6174,7 +6343,7 @@ dependencies = [ [[package]] name = "timsseek" -version = "0.27.0" +version = "0.28.0" dependencies = [ "arrow", "calibrt", @@ -6198,7 +6367,7 @@ dependencies = [ [[package]] name = "timsseek_cli" -version = "0.27.0" +version = "0.28.0" dependencies = [ "clap", "indicatif", @@ -6302,6 +6471,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -6325,6 +6504,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -6334,6 +6534,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.25.11+spec-1.1.0" @@ -6341,7 +6555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "winnow 1.0.1", ] @@ -6355,6 +6569,12 @@ dependencies = [ "winnow 1.0.1", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.3" @@ -7024,7 +7244,7 @@ checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" dependencies = [ "arrayvec", "bit-set", - "bit-vec", + "bit-vec 0.8.0", "bitflags 2.11.0", "bytemuck", "cfg_aliases", @@ -7295,6 +7515,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 545df67..939f3d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "rust/timsquery", "rust/timsquery_cli", "rust/timsquery_viewer", + "rust/speclib_build_cli", "python/timsquery_pyo3" ] default-members = [ @@ -21,11 +22,12 @@ default-members = [ "rust/timsseek_cli", "rust/timsquery", "rust/timsquery_cli", - "rust/timsquery_viewer" + "rust/timsquery_viewer", + "rust/speclib_build_cli" ] [workspace.package] -version = "0.27.0" +version = "0.28.0" edition = "2024" authors = ["Sebastian Paez"] license = "Apache-2.0" diff --git a/README.md b/README.md index d867896..4fd9374 100644 --- a/README.md +++ b/README.md @@ -78,15 +78,13 @@ cat << EOF > config_use.json } EOF -# Build the spectral lib -# Rn the models for RT+mobility are pretty rudimentary and -# hard-coded for a 22 min gradient, we can improve them in the future. -uv run speclib_build_fasta \ - --fasta_file $FASTA_FILE \ - --decoy_strategy REVERSE \ - --max_ions 10 \ - --outfile $SPECLIB_NAME \ - --model onnx +# Build the spectral lib using Koina (Prosit) for fragment/RT prediction. +# Requires network access to https://koina.wilhelmlab.org or a local Koina server. +cargo run --release -p speclib_build_cli -- \ + --fasta $FASTA_FILE \ + --fixed-mod "C[U:4]" \ + --max-ions 10 \ + -o $SPECLIB_NAME # Run timsseek using the generated speclib + config cargo run --release --bin timsseek -- \ diff --git a/Taskfile.yml b/Taskfile.yml index 5fd9268..c1b5953 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -48,3 +48,32 @@ tasks: - cross build --release --target x86_64-unknown-linux-gnu --bin timsseek - docker build --platform linux/amd64 -t timsseek:local . + speclib:build: + desc: Build spectral library from FASTA + cmds: + - cargo run --release -p speclib_build_cli -- {{.CLI_ARGS}} + + speclib:local-koina: + desc: Start local Koina server (CPU, Docker). First run downloads all models (~10-30 min). + cmds: + - | + if docker ps -a --format '{{.Names}}' | grep -q '^koina-local$'; then + echo "Restarting existing koina-local container (models already downloaded)" + docker start koina-local + else + echo "Creating new koina-local container (will download models from Zenodo)" + docker run -d \ + --name koina-local \ + --shm-size 8G \ + -p 8500:8500 -p 8501:8501 -p 8502:8502 \ + ghcr.io/wilhelm-lab/koina:latest + fi + - echo "Koina running at http://localhost:8501/v2/models" + - echo "Use --koina-url http://localhost:8501/v2/models" + - 'echo "Check readiness: curl -s http://localhost:8501/v2/health/ready"' + + speclib:stop-koina: + desc: Stop local Koina server + cmds: + - docker stop koina-local + diff --git a/bench/wandb_bench.py b/bench/wandb_bench.py index 14f46a6..78caf5f 100644 --- a/bench/wandb_bench.py +++ b/bench/wandb_bench.py @@ -29,6 +29,7 @@ class TimsseekRunner: speclib_location: Path raw_file_location: Path config_dict: dict[str, Any] | None = None + koina_url: str | None = None def build_speclib(self): if self.speclib_location.exists(): @@ -37,20 +38,26 @@ def build_speclib(self): logger.info("Building speclib") args = [ - "uv", + "cargo", "run", - "speclib_build_fasta", - "--fasta_file", + "--release", + "-p", + "speclib_build_cli", + "--", + "--fasta", str(self.fasta_file_location), - "--decoy_strategy", - "REVERSE", - "--max_ions", + "--fixed-mod", + "C[U:4]", + "--max-ions", "10", - "--outfile", + "-o", str(self.speclib_location), - "--model", - "onnx", ] + if self.koina_url: + args.extend(["--koina-url", self.koina_url]) + else: + # Public Koina: use delay to avoid rate limiting + args.extend(["--request-delay-ms", "500"]) res = subprocess.run(args, check=True) return res @@ -170,7 +177,6 @@ def default_timsseek_config(): "ms": {"ppm": [15.0, 15.0]}, "mobility": {"percent": [10.0, 10.0]}, "quad": {"absolute": [0.1, 0.1]}, - "rt": {"minutes": [5, 5]}, }, } } @@ -214,7 +220,7 @@ def wandb_context(config_dict: dict[str, Any], wandb_kwargs=None): run.finish() -def main(wandb_kwargs: dict | None = None): +def main(wandb_kwargs: dict | None = None, koina_url: str | None = None): fasta_file = Path.home() / "fasta/hela_gt20peps.fasta" speclib_path = Path.home() / "fasta/asdad.msgpack.zstd" @@ -232,6 +238,7 @@ def main(wandb_kwargs: dict | None = None): fasta_file_location=fasta_file, speclib_location=speclib_path, raw_file_location=file, + koina_url=koina_url, ) runner.build_speclib() runner.run(wandb_kwargs=wandb_kwargs) @@ -244,6 +251,12 @@ def build_parser(): type=str, help="The notes to add to the wandb run", ) + parser.add_argument( + "--koina-url", + type=str, + default=None, + help="Koina server URL (e.g. http://localhost:8501/v2/models for local)", + ) return parser @@ -258,4 +271,4 @@ def build_parser(): if args.notes is not None: wandb_kwargs["notes"] = args.notes - main(wandb_kwargs=wandb_kwargs) + main(wandb_kwargs=wandb_kwargs, koina_url=args.koina_url) diff --git a/example_speclib_config.toml b/example_speclib_config.toml new file mode 100644 index 0000000..717afdc --- /dev/null +++ b/example_speclib_config.toml @@ -0,0 +1,82 @@ +# example_speclib_config.toml +# +# Reference configuration for speclib_build. +# CLI flags override any value set here. +# All fields are optional — omit a section or key to use the compiled-in default. + +# ── Output ──────────────────────────────────────────────────────────────────── +# Path for the output spectral library (msgpack + zstd). +output = "library.msgpack.zst" + +# ── Digestion ───────────────────────────────────────────────────────────────── +[digestion] +# Proteolytic enzyme. Currently only "trypsin" is supported. +enzyme = "trypsin" + +# Peptide length range (number of amino acids, inclusive). +min_length = 7 +max_length = 25 + +# Maximum number of missed cleavage sites allowed per peptide. +missed_cleavages = 1 + +# ── Modifications ───────────────────────────────────────────────────────────── +[modifications] +# Fixed modifications applied to every matching residue. +# Format: "ModName@Residue" (ProForma-style notation) +fixed = ["Carbamidomethyl@C"] + +# Variable modifications; combinations up to max_variable are generated. +variable = ["Oxidation@M"] + +# Maximum number of variable modifications per peptide sequence. +max_variable = 2 + +# ── Charges ─────────────────────────────────────────────────────────────────── +[charges] +# Precursor charge range (inclusive). +min = 2 +max = 4 + +# ── Decoys ──────────────────────────────────────────────────────────────────── +[decoys] +# Decoy generation strategy. +# none — no decoys (target-only library) +# reverse — reverse the amino-acid sequence +# edge_mutate — mutate N- and C-terminal residues +strategy = "none" + +# ── Prediction ──────────────────────────────────────────────────────────────── +[prediction] +# Koina model names for fragment intensities and retention time. +fragment_model = "Prosit_2023_intensity_timsTOF" +rt_model = "Prosit_2019_irt" + +# Base URL for the Koina inference service (no trailing slash). +koina_url = "https://koina.wilhelmlab.org/v2/models" + +# Number of peptides sent to Koina per HTTP request. +batch_size = 1000 + +# Normalised collision energy forwarded to the fragment model (0.0 – 1.0). +nce = 0.3 + +# Delay in milliseconds between Koina request batches (0 = no delay). +# Useful to avoid rate-limiting on the public Koina server. +request_delay_ms = 0 + +# ── Filters ─────────────────────────────────────────────────────────────────── +[filters] +# Maximum number of fragment ions retained per precursor (highest intensity first). +max_ions = 10 + +# Precursor m/z window. +min_mz = 400.0 +max_mz = 2000.0 + +# Fragment ion m/z window; ions outside this range are discarded. +min_ion_mz = 250.0 +max_ion_mz = 2000.0 + +# Precursors with fewer surviving fragment ions than this are dropped. +min_ions = 3 diff --git a/pyproject.toml b/pyproject.toml index 8eb46a6..d1f26dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,8 @@ [project] name = "timsseek-workspace" -version = "0.27.0" +version = "0.28.0" requires-python = ">=3.11,<3.13" -dependencies = [ - "jupyter[python]>=1.1.1", - "speclib_builder[ml]", -] +dependencies = [] [dependency-groups] dev = [ @@ -20,18 +17,9 @@ interactive = [ "pandas", "polars", "matplotlib", - "ipykernel", "vizta", ] -[tool.uv.sources] -speclib_builder = { workspace = true } - -[tool.uv.workspace] -members = [ - "python/speclib_builder", -] - [tool.ruff] target-version = "py312" @@ -42,13 +30,8 @@ preview = true [tool.ruff.lint] select = ["E", "F", "T20", "I"] -[tool.hatch.build.targets.wheel] -packages = [ - "python/speclib_builder", -] - [tool.bumpver] -current_version = "0.27.0" +current_version = "0.28.0" version_pattern = "MAJOR.MINOR.PATCH[-PYTAGNUM]" tag_message = "v{new_version}" commit_message = "chore: bump version to {new_version}" diff --git a/python/speclib_builder/.gitignore b/python/speclib_builder/.gitignore deleted file mode 100644 index a62becf..0000000 --- a/python/speclib_builder/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*/*.csv -*/*.png -*.ndjson \ No newline at end of file diff --git a/python/speclib_builder/pyproject.toml b/python/speclib_builder/pyproject.toml deleted file mode 100644 index f244058..0000000 --- a/python/speclib_builder/pyproject.toml +++ /dev/null @@ -1,41 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "speclib_builder" -version = "0.27.0" -requires-python = ">=3.11,<3.14" -dependencies = [ - "rich", - "tqdm", - "pyteomics", - "numpy >= 2, < 3", - "rustyms", - "polars", - "loguru", - "uniplot", - "pydantic >= 2.11.4, < 3", - "zstandard", - "msgpack", -] - -[project.scripts] -speclib_build_fasta = "speclib_builder.fasta_cli:main" -speclib_convert = "speclib_builder.convert:main" - -[project.optional-dependencies] -ml = [ - "elfragmentadonnx", - "elfragmentador_core", - "cloudpathlib[s3]", - "boto3", -] - -[tool.hatch.build.targets.wheel] -only-packages = true - -[tool.uv.sources] -# TODO: publish this package ... maybe ... -elfragmentadonnx = { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentadonnx-0.23.0-py3-none-any.whl" } -elfragmentador_core = { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentador_core-0.23.0-py3-none-any.whl" } diff --git a/python/speclib_builder/speclib_builder/__init__.py b/python/speclib_builder/speclib_builder/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/speclib_builder/speclib_builder/__main__.py b/python/speclib_builder/speclib_builder/__main__.py deleted file mode 100644 index aeebebf..0000000 --- a/python/speclib_builder/speclib_builder/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .fasta_cli import main - -if __name__ == "__main__": - main() diff --git a/python/speclib_builder/speclib_builder/base.py b/python/speclib_builder/speclib_builder/base.py deleted file mode 100644 index fac5653..0000000 --- a/python/speclib_builder/speclib_builder/base.py +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import annotations - -import warnings - -from pydantic import BaseModel, ConfigDict -from rustyms import FragmentationModel, LinearPeptide - -from .decoys import DecoyStrategy, build_massshift_dict - -PROTON_MASS = 1.007276466 -NEUTRON_MASS = 1.008664916 - - -class PeptideElement(BaseModel): - peptide: str - charge: int - nce: float - decoy: bool - decoy_group: int - - -class MzIntPair(BaseModel): - mz: float - intensity: float - - -class PrecursorEntry(BaseModel): - sequence: str - charge: int - decoy: bool - decoy_group: int - - -class ElutionGroup(BaseModel): - id: int - mobility: float - rt_seconds: float - precursor_mz: float - precursor_charge: int - precursor_labels: list[int] - fragment_mzs: list[float] - fragment_labels: list[str] - - # Note that for the speclib entry, the intensities are required - # but not for timsquery/as an output - precursor_intensities: list[float] | None = None - fragment_intensities: list[float] | None = None - - -class SpeclibElement(BaseModel): - precursor: PrecursorEntry - elution_group: ElutionGroup - - def swap_peptide( - self, - proforma: str, - decoy: bool, - id: int, - fragmentation_model: FragmentationModel = FragmentationModel.CidHcd, - ) -> SpeclibElement: - """Swap the peptide for an elution group. - - This function also swaps the masses of the ions that are contained in the - current elution group. - - Notably, Right now it does not change the precursor mass.... or anything - other than the fragment m/z values. - """ - peptide = LinearPeptide(proforma) - curr_fragment_mzs = self.elution_group.fragment_mzs - curr_fragment_labels = self.elution_group.fragment_labels - frags = peptide.generate_theoretical_fragments( - peptide.charge - 1, fragmentation_model - ) - frags = [x for x in frags if x.neutral_loss is None] - ion_mass_dict = { - f"{f.ion}^{f.charge}": f.formula.mass() / f.charge for f in frags - } - - keep = {} - for k, v in zip(curr_fragment_labels, curr_fragment_mzs): - if k in ion_mass_dict: - keep[k] = ion_mass_dict[k] - else: - warnings.warn(f"Fragment {k} not found in new peptide {proforma}.") - - out = SpeclibElement( - precursor=PrecursorEntry( - sequence=proforma, - charge=self.precursor.charge, - decoy=decoy, - decoy_group=self.precursor.decoy_group, - ), - elution_group=ElutionGroup( - id=id, - mobility=self.elution_group.mobility, - rt_seconds=self.elution_group.rt_seconds, - fragment_mzs=[v for v in keep.values()], - fragment_labels=[k for k in keep.keys()], - precursor_labels=self.elution_group.precursor_labels, - precursor_mz=self.elution_group.precursor_mz, - precursor_charge=self.elution_group.precursor_charge, - precursor_intensities=self.elution_group.precursor_intensities, - fragment_intensities=self.elution_group.fragment_intensities, - ), - ) - - return out - - def generate_massshift_decoy( - self, - id: int, - decoy_strategy: DecoyStrategy, - fragmentation_model: FragmentationModel = FragmentationModel.CidHcd, - ) -> SpeclibElement: - massshift_dict, new_seq = build_massshift_dict( - self.precursor.sequence, - decoy_strategy, - max_charge=self.precursor.charge, - fragmentation_model=fragmentation_model, - ) - - new_fragment_mzs = [] - for k, v in zip( - self.elution_group.fragment_labels, - self.elution_group.fragment_mzs, - ): - new_fragment_mzs.append(v + massshift_dict[k]) - - return SpeclibElement( - precursor=PrecursorEntry( - sequence=new_seq, - charge=self.precursor.charge, - decoy=True, - decoy_group=self.precursor.decoy_group, - ), - elution_group=ElutionGroup( - id=id, - mobility=self.elution_group.mobility, - rt_seconds=self.elution_group.rt_seconds, - precursor_mz=self.elution_group.precursor_mz, - precursor_charge=self.elution_group.precursor_charge, - fragment_mzs=new_fragment_mzs, - precursor_labels=self.elution_group.precursor_labels, - fragment_labels=self.elution_group.fragment_labels, - precursor_intensities=self.elution_group.precursor_intensities, - fragment_intensities=self.elution_group.fragment_intensities, - ), - ) - - -class EntryElements(BaseModel): - model_config = ConfigDict(arbitrary_types_allowed=True) - - peptide: LinearPeptide - ion_dict: dict[str, MzIntPair] - rt_seconds: float - decoy: bool - id: int - decoy_group: int diff --git a/python/speclib_builder/speclib_builder/builder.py b/python/speclib_builder/speclib_builder/builder.py deleted file mode 100644 index 2c3041f..0000000 --- a/python/speclib_builder/speclib_builder/builder.py +++ /dev/null @@ -1,158 +0,0 @@ -from dataclasses import dataclass -from typing import Generator - -import rustyms -from rustyms import LinearPeptide - -from .base import ( - NEUTRON_MASS, - PROTON_MASS, - ElutionGroup, - EntryElements, - MzIntPair, - PeptideElement, - PrecursorEntry, - SpeclibElement, -) -from .isotopes import peptide_formula_dist -from .ml.ims import supersimpleprediction - - -@dataclass -class PeptideAnnotator: - def model(self, PeptideElement) -> EntryElements: - raise NotImplementedError - - def batched_model( - self, elements: list[PeptideElement] - ) -> Generator[EntryElements, None, None]: - for elem in elements: - yield self.model(elem) - - -class DummyAnnotator(PeptideAnnotator): - fragmentation_model: rustyms.FragmentationModel = rustyms.FragmentationModel.CidHcd - - def model(self, elem: PeptideElement) -> EntryElements: - seq = elem.peptide - charge = elem.charge - decoy = elem.decoy - decoy_group = elem.decoy_group - - pep = rustyms.LinearPeptide(f"{seq}/{charge}") - frags = pep.generate_theoretical_fragments( - pep.charge - 1, self.fragmentation_model - ) - frags = [x for x in frags if x.neutral_loss is None] - frags = [x for x in frags if x.charge <= (pep.charge - 1)] - ion_dict = { - f"{f.ion}^{f.charge}": MzIntPair( - mz=f.formula.mass() / f.charge, intensity=1.0 - ) - for f in frags - } - return EntryElements( - peptide=pep, - ion_dict=ion_dict, - decoy=decoy, - decoy_group=decoy_group, - rt_seconds=0, - id=0, - ) - - -@dataclass -class EntryBuilder: - max_ions_keep: int = 20 - min_mz: float = 400 - max_mz: float = 2000 - min_ion_mz: float = 250 - max_ion_mz: float = 2000 - min_ions: int = 3 - - def build_entry(self, elem: EntryElements) -> SpeclibElement | None: - return self.as_entry( - peptide=elem.peptide, - rt_seconds=elem.rt_seconds, - ion_dict=elem.ion_dict, - decoy=elem.decoy, - id=elem.id, - decoy_group=elem.decoy_group, - ) - - def as_entry( - self, - *, - peptide: LinearPeptide, - rt_seconds: float, - ion_dict: dict[str, MzIntPair], - decoy: bool, - id: int, - decoy_group: int, - ) -> SpeclibElement | None: - pep_formula = peptide.formula()[0] - isotope_dist = peptide_formula_dist(pep_formula) - precursor_mz = ( - pep_formula.mass() + (PROTON_MASS * peptide.charge) - ) / peptide.charge - if precursor_mz < self.min_mz or precursor_mz > self.max_mz: - return None - - ims = float(supersimpleprediction(precursor_mz, peptide.charge)) - - neutron_fraction = NEUTRON_MASS / peptide.charge - isotopes = [0, 1, 2] - precursor_mz = float(precursor_mz) - - intensities = [v.intensity for v in ion_dict.values()] - intensities.sort(reverse=True) - max_intensity = intensities[0] - min_inten_keep = max_intensity * 0.02 - intensities = [x for x in intensities if x > min_inten_keep] - if len(intensities) > self.max_ions_keep: - intensities = intensities[: self.max_ions_keep] - - min_inten_keep = intensities[-1] - - ion_dict = { - k: v - for k, v in ion_dict.items() - if (v.mz > self.min_ion_mz) - and (v.intensity >= min_inten_keep) - and (v.mz < self.max_ion_mz) - } - - ion_mzs = [v.mz for k, v in ion_dict.items()] - ion_keys = [k for k, v in ion_dict.items()] - ion_intensities = [v.intensity for k, v in ion_dict.items()] - if len(ion_mzs) < self.min_ions: - return None - - precursor_entry = { - "sequence": str(peptide), - "charge": peptide.charge, - "decoy": decoy, - "decoy_group": decoy_group, - } - elution_group = { - "id": id, - "mobility": ims, - "rt_seconds": rt_seconds, - # "precursors": precursor_mzs, - # "fragments": ion_mzs, - "precursor_labels": isotopes, - "precursor_mz": precursor_mz, - "precursor_charge": peptide.charge, - "fragment_labels": ion_keys, - "fragment_mzs": ion_mzs, - } - expected_intensities = { - "precursor_intensities": [v for _k, v in enumerate(isotope_dist)], - "fragment_intensities": ion_intensities, - } - - entry = SpeclibElement( - precursor=PrecursorEntry(**precursor_entry), - elution_group=ElutionGroup(**elution_group, **expected_intensities), - ) - return entry diff --git a/python/speclib_builder/speclib_builder/convert.py b/python/speclib_builder/speclib_builder/convert.py deleted file mode 100644 index 42b7fdd..0000000 --- a/python/speclib_builder/speclib_builder/convert.py +++ /dev/null @@ -1,308 +0,0 @@ -import argparse -import itertools -import multiprocessing -import os -from dataclasses import dataclass -from typing import Generator - -import rustyms -from tqdm.auto import tqdm - -from speclib_builder.base import ( - NEUTRON_MASS, - ElutionGroup, - MzIntPair, - PrecursorEntry, - SpeclibElement, -) - -from .decoys import DecoyStrategy -from .isotopes import peptide_formula_dist -from .writer import SpeclibWriter - - -def build_parser(): - parser = argparse.ArgumentParser() - parser.add_argument("--input", type=str, default="speclib.txt") - # parser.add_argument("--input_format", type=str, default="diann_speclib") - parser.add_argument("--add_decoys", action="store_true") - parser.add_argument("--output", type=str, default="output.ndjson") - parser.add_argument("--num_workers", type=int, default=multiprocessing.cpu_count()) - return parser - - -@dataclass -class DiannTransitionGroup: - transition_group_id: str - file_name: str - precursor_mz: float - ion_mobility: float - decoy: bool - stripped_sequence: str - modified_sequence: str - charge: int - ion_dict: dict[str, MzIntPair] - - def as_speclib_element(self, int_id: int): - neutron_mass_frac = NEUTRON_MASS / self.charge - proforma = self.modified_sequence + f"/{self.charge}" - rs_peptide = rustyms.LinearPeptide(proforma) - iso_dist = [0.001] + list(peptide_formula_dist(rs_peptide.formula()[0])) - return SpeclibElement( - precursor=PrecursorEntry( - sequence=proforma, - charge=self.charge, - decoy=self.decoy, - ), - elution_group=ElutionGroup( - id=int_id, - mobility=self.ion_mobility, - rt_seconds=0, - precursor_mzs=[ - self.precursor_mz + (neutron_mass_frac * isotope) - for isotope in [-1, 0, 1, 2] - ], - fragment_mzs={k: v.mz for k, v in self.ion_dict.items()}, - fragment_intensities={k: v.intensity for k, v in self.ion_dict.items()}, - precursor_intensities={i: id for i, id in zip(iso_dist, [-1, 0, 1, 2])}, - ), - ) - - -def diann_pep_to_proforma(pep: str) -> str: - return pep.replace("(UniMod:", "[U:").replace(")", "]") - - -class DiannTransitionGroupBuilder: - def __init__(self): - self.transition_group_id = None - self.file_name = None - self.precursor_mz = None - self.ion_mobility = None - self.decoy = None - self.stripped_sequence = None - self.modified_sequence = None - self.charge = None - self.ions = [] - self.mzs = [] - self.intensities = [] - - @classmethod - def new(cls, values: dict) -> "DiannTransitionGroupBuilder": - builder = cls() - builder.transition_group_id = values["transition_group_id"] - builder.file_name = values["FileName"] - builder.precursor_mz = float(values["PrecursorMz"]) - builder.ion_mobility = float(values["IonMobility"]) - builder.decoy = bool(int(values["decoy"])) - builder.stripped_sequence = values["PeptideSequence"] - builder.modified_sequence = diann_pep_to_proforma(values["ModifiedPeptide"]) - builder.charge = int(values["PrecursorCharge"]) - builder.add_ion(values) - return builder - - def add_ion(self, values: dict) -> None: - ion = f"{values['FragmentType']}{values['FragmentSeriesNumber']}^{values['FragmentCharge']}" - self.ions.append(ion) - self.mzs.append(float(values["ProductMz"])) - self.intensities.append(float(values["LibraryIntensity"])) - - def build(self) -> DiannTransitionGroup: - return DiannTransitionGroup( - transition_group_id=self.transition_group_id, - file_name=self.file_name, - precursor_mz=self.precursor_mz, - ion_mobility=self.ion_mobility, - decoy=self.decoy, - stripped_sequence=self.stripped_sequence, - modified_sequence=self.modified_sequence, - charge=self.charge, - ion_dict={ - ion: MzIntPair(mz, inten) - for ion, mz, inten in zip( - self.ions, - self.mzs, - self.intensities, - strict=True, - ) - }, - ) - - -def bundle_precursors(file) -> Generator[DiannTransitionGroup, None, None]: - last_id = None - last_filename = None - current_builder = None - - with open(file) as f: - # Get column positions from header - header = f.readline().strip().split("\t") - col_idx = {name: i for i, name in enumerate(header)} - - for line in f: - # Split the line and get key fields - values = line.strip().split("\t") - row_dict = {name: values[i] for name, i in col_idx.items()} - curr_id = row_dict["transition_group_id"] - curr_filename = row_dict["FileName"] - - # If we've hit a new group, yield the previous one - if last_id is not None and ( - curr_id != last_id or curr_filename != last_filename - ): - yield current_builder.build() - current_builder = DiannTransitionGroupBuilder.new(row_dict) - elif current_builder is None: - current_builder = DiannTransitionGroupBuilder.new(row_dict) - else: - current_builder.add_ion(row_dict) - - last_id = curr_id - last_filename = curr_filename - - # Don't forget to yield the last group - if current_builder is not None: - yield current_builder.build() - - -def _aprox_count_lines(file): - # Estimate number of lines by reading the 2-200th line - # and counting the bytes in them. THEN divide the total - # file size by the average line size. - byte_sizes = [] - tot_file_size = os.path.getsize(file) - - with open(file) as f: - last = None - count = 0 - local_char_size = 0 - for line in f: - local_char_size += len(line) - curr = tuple(line.split("\t")[:2]) - if curr != last: - count += 1 - last = curr - byte_sizes.append(local_char_size) - local_char_size = 0 - - if count > 20000: - break - - if count < 20000: - return count - - byte_sizes = byte_sizes[1:] - mean_byte_size = sum(byte_sizes) / len(byte_sizes) - count = int(tot_file_size / mean_byte_size) - - return count - - -def _count_lines(file): - with open(file) as f: - last = None - count = 0 - for line in tqdm(f, desc="Counting lines"): - curr = tuple(line.split("\t")[:2]) - if curr != last: - count += 1 - last = curr - - return count - - -def process_element(args): - x, start_id, add_decoys = args - elem = x.as_speclib_element(start_id) - - decoy_elem = None - if add_decoys: - if elem.precursor.decoy: - raise ValueError("Cannot add decoys to decoys") - decoy_elem = elem.generate_massshift_decoy( - id=start_id + 1, - decoy_strategy=DecoyStrategy.REVERSE, - ) - - return (elem, decoy_elem) - - -def _parallel_process(args, writer, nlines, max_workers): - with writer as f: - precursors = list() - - with multiprocessing.Pool(max_workers) as pool: - # Create arguments for each precursor - precs = bundle_precursors(args.input) - ranges = _infinite_range(0, 2 if args.add_decoys else 1) - process_args = zip( - precs, - ranges, - itertools.repeat(args.add_decoys), - ) - inner_iter = pool.imap_unordered(process_element, process_args) - - # Process and write results as they come in - pbar = tqdm( - inner_iter, - total=len(precursors), - desc="Converting targets", - ) - # The chaining is required bc the "total" in tqdm can be smaller - # than the number of precursors, which makes it truncate prematurely. - # The chain makes sure we process the rest. - for result_group in itertools.chain(pbar, tqdm(inner_iter)): - for elem in result_group: - if elem is not None: - f.append(elem) - - -def _infinite_range(start, step): - while True: - yield start - start += step - - -def main(): - args = build_parser().parse_args() - writer = SpeclibWriter(args.output) - aprox_lines = _aprox_count_lines(args.input) - print(f"Approximate number of entries: {aprox_lines}") - if args.num_workers >= 2: - print(f"Using {args.num_workers} workers") - _parallel_process(args, writer, aprox_lines, args.num_workers) - else: - print( - "Using serial processing (pass " - "--num_workers > 2 to use parallel processing)" - ) - _serial_process(args, writer, aprox_lines) - - -def _serial_process(args, writer, nlines): - with writer as f: - id = 0 - precs = bundle_precursors(args.input) - pbar = tqdm(precs, desc="Converting targets", total=nlines) - # The chaining is required bc the "total" in tqdm can be smaller - # than the number of precursors, which makes it truncate prematurely. - # The chain makes sure we process the rest. - for x in itertools.chain(pbar, tqdm(precs)): - elem = x.as_speclib_element(id) - f.append(elem) - id += 1 - if args.add_decoys: - if elem.precursor.decoy: - raise ValueError("Cannot add decoys to decoys") - decoy_elem = elem.generate_massshift_decoy( - id=id, - decoy_strategy=DecoyStrategy.REVERSE, - ) - f.append(decoy_elem) - id += 1 - - pbar.set_postfix({"written": id}) - - -if __name__ == "__main__": - main() diff --git a/python/speclib_builder/speclib_builder/decoys.py b/python/speclib_builder/speclib_builder/decoys.py deleted file mode 100644 index 1f82759..0000000 --- a/python/speclib_builder/speclib_builder/decoys.py +++ /dev/null @@ -1,106 +0,0 @@ -import enum -from typing import Generator, Literal - -import rustyms -from pyteomics.proforma import ProForma -from pyteomics.proforma import parse as proforma_parse - -MUTATE_DICT = {} -for old, new in zip("GAVLIFMPWSCTYHKRQEND", "LLLVVLLLLTSSSSLLNDQE"): - MUTATE_DICT[old] = new - - -class DecoyStrategy(enum.Enum): - REVERSE = "REVERSE" - MUTATE = "MUTATE" - EDGE_MUTATE = "EDGE_MUTATE" - - -MIN_ORD = ord("A") -MAX_ORD = ord("Z") - - -def as_decoy(x: str, decoy_strategy: DecoyStrategy) -> str: - for c in x: - if ord(c) < MIN_ORD or ord(c) > MAX_ORD: - return as_decoy_proforma(x, decoy_strategy) - - if decoy_strategy == DecoyStrategy.REVERSE: - out = x[0] + x[-2:0:-1] + x[-1] - elif decoy_strategy == DecoyStrategy.MUTATE: - out = "".join([MUTATE_DICT[w] for w in x]) - elif decoy_strategy == DecoyStrategy.EDGE_MUTATE: - out = "".join([x[0] + MUTATE_DICT[x[1]] + x[2:-2] + MUTATE_DICT[x[-2]] + x[-1]]) - else: - raise NotImplementedError(f"Unknown decoy strategy {decoy_strategy}") - return out - - -def as_decoy_proforma(proforma: str, decoy_strategy: DecoyStrategy) -> str: - parsed = proforma_parse(proforma) - inner_seq = parsed[0] - - if decoy_strategy == DecoyStrategy.REVERSE: - inner_seq = [inner_seq[0]] + inner_seq[-2:0:-1] + [inner_seq[-1]] - elif decoy_strategy == DecoyStrategy.MUTATE: - out = [] - for x in inner_seq: - new_aa = MUTATE_DICT[x[0]] - out.append((new_aa, x[1] if new_aa == x[0] else None)) - inner_seq = out - elif decoy_strategy == DecoyStrategy.EDGE_MUTATE: - new_aa_1 = MUTATE_DICT[inner_seq[1][0]] - new_aa_neg2 = MUTATE_DICT[inner_seq[-2][0]] - inner_seq[1] = ( - new_aa_1, - inner_seq[1][1] if new_aa_1 == inner_seq[1][0] else None, - ) - inner_seq[-2] = ( - new_aa_neg2, - inner_seq[-2][1] if new_aa_neg2 == inner_seq[-2][0] else None, - ) - - else: - raise NotImplementedError(f"Unknown decoy strategy {decoy_strategy}") - - tmp = ProForma(inner_seq, parsed[1]) - return str(tmp).replace("UNIMOD:", "U:") - - -def yield_with_decoys( - peptides: list[str], decoy_strategy: DecoyStrategy -) -> Generator[tuple[str, bool, int], None, None]: - for id, peptide in enumerate(peptides): - yield peptide, False, id - try: - yield as_decoy(peptide, decoy_strategy), True, id - except KeyError as e: - print(f"No decoy for {peptide} because KeyError: {e}") - - -def build_massshift_dict( - seq: str, - decoy_strategy: DecoyStrategy, - *, - max_charge: int = 3, - fragmentation_model: rustyms.FragmentationModel = rustyms.FragmentationModel.CidHcd, -) -> tuple[dict[str, float], str]: - fw_peptide = rustyms.LinearPeptide(seq) - fw_frags = fw_peptide.generate_theoretical_fragments( - max_charge, fragmentation_model - ) - fw_frags = [x for x in fw_frags if x.neutral_loss is None] - fw_ion_dict = {f"{f.ion}^{f.charge}": f.formula.mass() / f.charge for f in fw_frags} - - rev = as_decoy(seq, decoy_strategy) - rev_peptide = rustyms.LinearPeptide(rev) - rev_frags = rev_peptide.generate_theoretical_fragments( - max_charge, fragmentation_model - ) - rev_frags = [x for x in rev_frags if x.neutral_loss is None] - rev_ion_dict = { - f"{f.ion}^{f.charge}": f.formula.mass() / f.charge for f in rev_frags - } - - diff_dict = {k: rev_ion_dict[k] - fw_ion_dict[k] for k in fw_ion_dict} - return diff_dict, rev diff --git a/python/speclib_builder/speclib_builder/fasta.py b/python/speclib_builder/speclib_builder/fasta.py deleted file mode 100644 index d1482ec..0000000 --- a/python/speclib_builder/speclib_builder/fasta.py +++ /dev/null @@ -1,81 +0,0 @@ -from dataclasses import dataclass -from typing import Generator, Iterable - -from pyteomics import fasta, parser - -from .base import PeptideElement -from .decoys import DecoyStrategy, yield_with_decoys - - -def get_peptides(fasta_file: str) -> list[str]: - print("Cleaving the proteins with trypsin...") - unique_peptides = set() - nseqs = 0 - with open(fasta_file) as file: - for description, sequence in fasta.FASTA(file): - nseqs += 1 - new_peptides = parser.cleave( - sequence, - "trypsin", - min_length=6, - max_length=20, - missed_cleavages=1, - ) - unique_peptides.update(new_peptides) - - unique_peptides = list(unique_peptides) - print(unique_peptides[:5]) - print(f"Done, {len(unique_peptides)} sequences obtained from {nseqs} proteins!") - return unique_peptides - - -def yield_with_mods( - peptides: Iterable[tuple[str, bool, int]], -) -> Generator[tuple[str, bool, int], None, None]: - # TODO: implement more mods ... - for peptide in peptides: - yield (peptide[0].replace("C", "C[UNIMOD:4]"), peptide[1], peptide[2]) - - -def yield_with_charges( - peptides: Iterable[tuple[str, bool, int]], - min_charge, - max_charge, -) -> Generator[tuple[str, bool, int, int, float], None, None]: - for peptide in peptides: - for charge in range(min_charge, max_charge + 1): - yield (peptide[0], peptide[1], peptide[2], charge, 30.0) - - -# This is more a factory than a builder, but anyway ... -@dataclass -class PeptideBuilder: - fasta_file: str - min_charge: int - max_charge: int - decoy_strategy: DecoyStrategy - - def get_targets(self) -> list[str]: - peps = get_peptides(fasta_file=self.fasta_file) - return peps - - def get_modified_target_decoys(self) -> list[PeptideElement]: - targ_use = list( - yield_with_charges( - yield_with_mods( - yield_with_decoys(self.get_targets(), self.decoy_strategy) - ), - self.min_charge, - self.max_charge, - ) - ) - return [ - PeptideElement( - peptide=pep, - charge=charge, - nce=nce, - decoy=decoy, - decoy_group=decoy_group, - ) - for pep, decoy, decoy_group, charge, nce in targ_use - ] diff --git a/python/speclib_builder/speclib_builder/fasta_cli.py b/python/speclib_builder/speclib_builder/fasta_cli.py deleted file mode 100644 index f8d6e4f..0000000 --- a/python/speclib_builder/speclib_builder/fasta_cli.py +++ /dev/null @@ -1,163 +0,0 @@ -import enum -import json - -from rich.pretty import pprint -from tqdm.auto import tqdm -from pathlib import Path - -from .builder import DummyAnnotator, EntryBuilder -from .decoys import DecoyStrategy -from .fasta import PeptideBuilder -from .onxx_predictor import OnnxPeptideTransformerAnnotator -from .writer import SpeclibWriter - - -class ModelType(enum.Enum): - ONNX = "onnx" - DUMMY = "dummy" - - -def build_parser(): - import argparse - - # parser = argparse.ArgumentParser() - # Same as above but shows default values and such ... also prettyer using rich - parser = argparse.ArgumentParser( - description="Build a speclib from a fasta file", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - parser.add_argument( - "--fasta_file", - type=str, - default="/Users/sebastianpaez/fasta/20231030_UP000005640_9606.fasta", - help="Fasta file to use", - ) - parser.add_argument( - "--decoy_strategy", - type=str, - default="REVERSE", - choices=["REVERSE", "MUTATE", "EDGE_MUTATE"], - help="Decoy strategy to use", - ) - parser.add_argument( - "--max_ions", - type=int, - default=10, - help="Maximum number of ions to keep per precursor", - ) - parser.add_argument( - "--outfile", type=str, default="FUUUUU.msgpack.zst", help="Output file" - ) - parser.add_argument( - "--model", - type=str, - default="onnx", - help="Model to use, the dummy model just sets all intensities to 1", - choices=["onnx", "dummy"], - ) - return parser - - -def main(): - args = build_parser().parse_args() - - if args.decoy_strategy == "REVERSE": - decoy_strategy = DecoyStrategy.REVERSE - elif args.decoy_strategy == "MUTATE": - decoy_strategy = DecoyStrategy.MUTATE - elif args.decoy_strategy == "EDGE_MUTATE": - decoy_strategy = DecoyStrategy.EDGE_MUTATE - else: - raise NotImplementedError(f"Unknown decoy strategy {args.decoy_strategy}") - - if args.model == "onnx": - annotator = OnnxPeptideTransformerAnnotator.get_default() - elif args.model == "dummy": - annotator = DummyAnnotator() - else: - raise NotImplementedError(f"Unknown model {args.model}") - - fasta_file = args.fasta_file - outfile = args.outfile - max_keep = args.max_ions - - _main( - fasta_file=fasta_file, - outfile=outfile, - max_keep=max_keep, - decoy_strategy=decoy_strategy, - annotator=annotator, - ) - - -def _main( - *, - fasta_file: str, - outfile: str, - max_keep: int, - decoy_strategy: DecoyStrategy, - annotator: OnnxPeptideTransformerAnnotator | DummyAnnotator, - file_format: str = "msgpack_zstd", -): - pretty_outfile = str(Path(outfile).with_suffix("")) + ".pretty.json" - - peptide_builder = PeptideBuilder( - fasta_file=fasta_file, - min_charge=2, - max_charge=4, - decoy_strategy=decoy_strategy, - ) - - # # outfile = "FUUUUU_small.ndjson" - # outfile = "FUUUUU.ndjson" - # fasta_file = "/Users/sebastianpaez/fasta/20231030_UP000005640_9606.fasta" - # # fasta_file = "/Users/sebastianpaez/git/timsseek/data/HeLa_cannonical_proteins.fasta" - entry_builder = EntryBuilder( - min_ions=3, - max_ions_keep=max_keep, - min_mz=400, - max_mz=2000, - min_ion_mz=250, - max_ion_mz=2000, - ) - - pretty_outs = [] - is_first_n = 10 - id = 0 - - pprint(f"Writing output to file: {outfile}") - - with SpeclibWriter(path=Path(outfile), file_format=file_format) as writer: - targ_use = peptide_builder.get_modified_target_decoys() - for x in tqdm( - annotator.batched_model(targ_use), - desc="Targets", - total=len(targ_use), - ): - id += 1 - elem = entry_builder.as_entry( - peptide=x.peptide, - decoy=x.decoy, - id=id, - ion_dict=x.ion_dict, - rt_seconds=x.rt_seconds, - decoy_group=x.decoy_group, - ) - if elem is None: - continue - if is_first_n > 0: - pprint(elem) - pretty_outs.append(elem) - is_first_n -= 1 - - writer.append(elem) - - pprint(f"Writing pretty output to file: {pretty_outfile}") - with open(pretty_outfile, "w") as file: - file.write(json.dumps([x.model_dump() for x in pretty_outs], indent=4)) - file.flush() - - -if __name__ == "__main__": - # Run the test - main() diff --git a/python/speclib_builder/speclib_builder/isotopes.py b/python/speclib_builder/speclib_builder/isotopes.py deleted file mode 100644 index 6433b26..0000000 --- a/python/speclib_builder/speclib_builder/isotopes.py +++ /dev/null @@ -1,111 +0,0 @@ -# Credit where credit is due ... -# This is essentially a port of the original code from Sage -# and its immplementation of the isotope distribution calculation - -import numpy as np -from rustyms import MolecularFormula - - -def convolve(a: list[float], b: list[float]) -> tuple[float, float, float, float]: - """ - Performs a custom convolution operation on two arrays of length 4. - - Args: - a: First array of 4 floating point numbers - b: Second array of 4 floating point numbers - - Returns: - List of 4 floating point numbers representing the convolution result - """ - return ( - a[0] * b[0], - a[0] * b[1] + a[1] * b[0], - a[0] * b[2] + a[1] * b[1] + a[2] * b[0], - a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0], - ) - - -def carbon_isotopes(count: int) -> list[float]: - """ - Calculates carbon isotope distributions. - - Args: - count: Number of carbon atoms - - Returns: - List of 4 floating point numbers representing isotope distributions - """ - lambda_val = float(count) * 0.011 - c13 = [0.0] * 4 - fact = [1, 1, 2, 6] - - for k in range(4): - c13[k] = pow(lambda_val, k) * np.exp(-lambda_val) / float(fact[k]) - - return c13 - - -def sulfur_isotopes(count: int) -> tuple[float, float, float, float]: - """ - Calculates sulfur isotope distributions. - - Args: - count: Number of sulfur atoms - - Returns: - List of 4 floating point numbers representing convolved isotope distributions - """ - lambda33 = float(count) * 0.0076 - lambda35 = float(count) * 0.044 - s33 = [0.0] * 4 - s35 = [ - pow(lambda35, 0) * np.exp(-lambda35), - 0.0, - pow(lambda35, 1) * np.exp(-lambda35), - 0.0, - ] - - fact = [1, 1, 2, 6] - for k in range(4): - s33[k] = pow(lambda33, k) * np.exp(-lambda33) / float(fact[k]) - - return convolve(s33, s35) - - -def peptide_isotopes(carbons: int, sulfurs: int) -> tuple[float, float, float]: - """ - Calculates peptide isotope distributions based on number of carbon and sulfur atoms. - - Args: - carbons: Number of carbon atoms - sulfurs: Number of sulfur atoms - - Returns: - List of 3 floating point numbers representing normalized isotope distributions - """ - c = carbon_isotopes(carbons) - s = sulfur_isotopes(sulfurs) - result = convolve(c, s) - max_val = max(result[:3]).item() # Only consider first 3 values for normalization - - # Normalize first 3 values - return [val.item() / max_val for val in result[:3]] - - -def peptide_formula_dist(formula: MolecularFormula) -> tuple[float, float, float]: - c_count = 0 - s_count = 0 - - for elem, _, count in formula.elements(): - str_elem = str(elem) - if str_elem == "C": - c_count = count - elif str_elem == "S": - s_count = count - else: - continue - - if c_count == 0: - raise ValueError("No carbons found in formula") - - return peptide_isotopes(c_count, s_count) diff --git a/python/speclib_builder/speclib_builder/ml/__init__.py b/python/speclib_builder/speclib_builder/ml/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/speclib_builder/speclib_builder/ml/adam.py b/python/speclib_builder/speclib_builder/ml/adam.py deleted file mode 100644 index fba6b3a..0000000 --- a/python/speclib_builder/speclib_builder/ml/adam.py +++ /dev/null @@ -1,57 +0,0 @@ -from dataclasses import dataclass, field - -import numpy as np - - -@dataclass -class AdamOptimizer: - learning_rate: float = 0.001 - beta1: float = 0.9 - beta2: float = 0.99 - epsilon: float = 1e-8 - m_w: np.ndarray = field(init=False) - v_w: np.ndarray = field(init=False) - m_b: float = field(init=False) - v_b: float = field(init=False) - t: int = field(default=0, init=False) - n_features: int = field(default=0, init=False) - - def init_params(self, n_features): - self.m_w = np.zeros(n_features) - self.v_w = np.zeros(n_features) - self.m_b = 0.0 - self.v_b = 0.0 - self.t = 0 - self.n_features = n_features - - def update(self, dw, db): - assert dw.shape == (self.n_features,), ( - f"Expected dw to have shape ({self.n_features},), got {dw.shape}" - ) - assert db.shape == () - self.t += 1 - - # Update moments - self.m_w = self.beta1 * self.m_w + (1 - self.beta1) * dw - self.v_w = self.beta2 * self.v_w + (1 - self.beta2) * np.square(dw) - self.m_b = self.beta1 * self.m_b + (1 - self.beta1) * db - self.v_b = self.beta2 * self.v_b + (1 - self.beta2) * (db**2) - - # Bias correction - m_w_hat = self.m_w / (1 - self.beta1**self.t) - v_w_hat = self.v_w / (1 - self.beta2**self.t) - m_b_hat = self.m_b / (1 - self.beta1**self.t) - v_b_hat = self.v_b / (1 - self.beta2**self.t) - - assert m_w_hat.shape == (self.n_features,) - assert v_w_hat.shape == (self.n_features,) - assert m_b_hat.shape == () - assert v_b_hat.shape == () - - # Compute updates - w_update = self.learning_rate * m_w_hat / (np.sqrt(v_w_hat) + self.epsilon) - b_update = self.learning_rate * m_b_hat / (np.sqrt(v_b_hat) + self.epsilon) - - assert w_update.shape == (self.n_features,) - - return w_update, b_update diff --git a/python/speclib_builder/speclib_builder/ml/ims.py b/python/speclib_builder/speclib_builder/ml/ims.py deleted file mode 100644 index 4e83d44..0000000 --- a/python/speclib_builder/speclib_builder/ml/ims.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import overload - -import numpy as np - -# from .linear_regression import LinearRegression -# from dataclasses import dataclass, field -# from rustyms import LinearPeptide -# from .ohe_peptide_embed import PeptideOHEEmbedder, FEATURES - - -@overload -def supersimpleprediction(mz: np.ndarray, charge: np.ndarray) -> np.ndarray: ... - - -@overload -def supersimpleprediction(mz: float, charge: int) -> float: ... - - -def supersimpleprediction(mz: float | np.ndarray, charge: int | np.ndarray): - intercept_ = -1.660e00 - log1p_mz = np.log1p(mz) - sq_mz_over_charge = (mz**2) / charge - log1p_sq_mz_over_charge = np.log1p(sq_mz_over_charge) - - out = ( - intercept_ - + (-3.798e-01 * log1p_mz) - + (-2.389e-04 * mz) - + (3.957e-01 * log1p_sq_mz_over_charge) - + (4.157e-07 * sq_mz_over_charge) - + (1.417e-01 * charge) - ) - return out diff --git a/python/speclib_builder/speclib_builder/ml/linear_regression.py b/python/speclib_builder/speclib_builder/ml/linear_regression.py deleted file mode 100644 index 77f6ba7..0000000 --- a/python/speclib_builder/speclib_builder/ml/linear_regression.py +++ /dev/null @@ -1,85 +0,0 @@ -import math -from dataclasses import dataclass - -import numpy as np -from uniplot import plot - -from .adam import AdamOptimizer - - -@dataclass -class LinearRegression: - weights: np.ndarray | None = None - bias: float | None = None - - def _compute_loss(self, y_true, y_pred): - return np.mean((y_true - y_pred) ** 2) - - def fit( - self, - X, - y, - *, - learning_rate: float = 0.01, - n_iterations: int = 100_000, - min_diff: float = 1e-6, - patience: int = 100, - plateau: int = 10, - verbose: bool = True, - ): - n_samples, n_features = X.shape - optimizer = AdamOptimizer(learning_rate=learning_rate) - optimizer.init_params(n_features) - loss_history = [] - self.weights = np.zeros(n_features) - self.bias = 0.0 - best_loss = np.inf - best_iter = 0 - - for i in range(n_iterations): - y_predicted = np.dot(X, self.weights) + self.bias - current_loss = self._compute_loss(y, y_predicted) - loss_history.append(current_loss) - - if i % 200 == 0 and verbose: - mae = np.mean(np.abs(y_predicted - y)) - print(f"Iteration {i}, Loss: {current_loss:.4f} MAE: {mae:.4f}") - - dw = (1 / math.sqrt(n_samples)) * np.dot(X.T, y_predicted - y) - db = (1 / math.sqrt(n_samples)) * np.sum(y_predicted - y) - - w_update, b_update = optimizer.update(dw, db) - self.weights -= w_update - self.bias -= b_update - - if current_loss < (best_loss - min_diff): - best_loss = current_loss - best_iter = i - - if i - best_iter > plateau: - if verbose: - print( - f"Plateau reached at iteration {i} with loss {current_loss:.6f}" - ) - learning_rate /= 2 - optimizer = AdamOptimizer(learning_rate=learning_rate) - optimizer.init_params(n_features) - - patience_count = i - best_iter - if patience_count > patience: - if verbose: - print( - f"Early stopping at iteration {i} with loss {current_loss:.6f}" - ) - break - - if verbose: - plot(np.log1p(loss_history), title="Loss History") - plot(y_predicted, y, title="Prediction vs Actual") - - return loss_history - - def predict(self, X): - if self.weights is None or self.bias is None: - raise ValueError("Model not fitted") - return np.dot(X, self.weights) + self.bias diff --git a/python/speclib_builder/speclib_builder/ml/loess.py b/python/speclib_builder/speclib_builder/ml/loess.py deleted file mode 100644 index 412b9bb..0000000 --- a/python/speclib_builder/speclib_builder/ml/loess.py +++ /dev/null @@ -1,61 +0,0 @@ -from dataclasses import dataclass - -import numpy as np -from uniplot import plot - -from .linear_regression import LinearRegression - - -@dataclass -class MultiPivotRegression: - offsets: np.ndarray | None = None - regressors: list[LinearRegression] | None = None - - def predict(self, X): - predictions = self._predict(X) - return predictions.sum(axis=0) - - def _predict(self, X): - predictions = np.array([ - reg.predict(X - np.delete(self.offsets.T, i)) - for i, reg in enumerate(self.regressors) - ]) - return predictions - - def fit( - self, - X, - y, - x_range: tuple[float, float], - n_kernels: int = 10, - learning_rate: float = 0.001, - n_iterations: int = 100, - ): - n_samples, n_features = X.shape - if self.offsets is None: - self.offsets = np.linspace(x_range[0], x_range[1], n_kernels) - - if self.regressors is None: - self.regressors = [LinearRegression() for _ in range(n_kernels)] - - for i, reg in enumerate(self.regressors): - reg.fit(X - np.delete(self.offsets.T, i), y, verbose=False) - - for i in range(n_iterations): - cp = self._predict(X) - for j, reg in enumerate(self.regressors): - rest = y - np.sum(np.delete(cp, j, axis=0), axis=0) - reg.fit( - X - np.delete(self.offsets.T, j), - rest, - verbose=False, - learning_rate=1e-4, - n_iterations=200, - ) - - pred = self.predict(X) - if i % 5 == 0: - print(f"Iteration {i}, Loss: {np.mean(np.abs(pred - y)):.4f}") - - plot(X.flatten(), y.flatten(), title="Loess") - plot(pred, y.flatten(), title="Loess") diff --git a/python/speclib_builder/speclib_builder/ml/ohe_peptide_embed.py b/python/speclib_builder/speclib_builder/ml/ohe_peptide_embed.py deleted file mode 100644 index e0938fb..0000000 --- a/python/speclib_builder/speclib_builder/ml/ohe_peptide_embed.py +++ /dev/null @@ -1,40 +0,0 @@ -import math - -import numpy as np -from rustyms import LinearPeptide - -# Model pretty much translating the implementation from sage - -VALID_AA: str = "ACDEFGHIKLMNPQRSTVWY" -FEATURES: int = len(VALID_AA) * 3 + 2 -N_TERMINAL: int = len(VALID_AA) -C_TERMINAL: int = len(VALID_AA) * 2 -PEPTIDE_LEN: int = FEATURES - 3 -PEPTIDE_MASS_LN: int = FEATURES - 2 -INTERCEPT: int = FEATURES - 1 - - -class PeptideOHEEmbedder: - def embed(self, peptide: LinearPeptide) -> np.ndarray: - stripped_seq = peptide.stripped_sequence - mass = peptide.formula()[0].monoisotopic_mass() - return self.embed_stripped_sequence(stripped_seq, mass) - - def embed_stripped_sequence(self, stripped_seq: str, mass: float) -> np.ndarray: - embedding = [0.0] * FEATURES - for aa_idx, residue in enumerate(stripped_seq): - try: - idx = VALID_AA.index(residue) - except ValueError: - raise ValueError(f"Invalid residue {residue} at position {aa_idx}") - embedding[idx] += 1.0 - # Embed N- and C-terminal AA's (2 on each end, excluding K/R) - if aa_idx <= 1: - embedding[N_TERMINAL + idx] += 1.0 - elif aa_idx >= len(stripped_seq) - 2: - embedding[C_TERMINAL + idx] += 1.0 - embedding[PEPTIDE_LEN] = len(stripped_seq) - embedding[PEPTIDE_MASS_LN] = math.log1p(mass) - embedding[INTERCEPT] = 1.0 - - return np.array(embedding) diff --git a/python/speclib_builder/speclib_builder/ml/simplertmodel.py b/python/speclib_builder/speclib_builder/ml/simplertmodel.py deleted file mode 100644 index 92e528b..0000000 --- a/python/speclib_builder/speclib_builder/ml/simplertmodel.py +++ /dev/null @@ -1,119 +0,0 @@ -import numpy as np -from rustyms import LinearPeptide - -from .linear_regression import LinearRegression -from .ohe_peptide_embed import FEATURES, PeptideOHEEmbedder - - -class SimpleRTModel(LinearRegression): - def __init__(self, weights: np.ndarray | None = None, bias: float | None = None): - super().__init__(weights, bias) - self.embedder = PeptideOHEEmbedder() - - def predict_peptide(self, peptide: LinearPeptide) -> float: - return self.predict(self.embedder.embed(peptide)).item() - - def preict_peptides(self, peptides: list[LinearPeptide]) -> np.ndarray: - embeddings = np.stack( - [self.embedder.embed(peptide) for peptide in peptides], axis=0 - ) - assert embeddings.shape[0] == len(peptides) - assert embeddings.shape[1] == FEATURES - assert len(embeddings.shape) == 2 - return self.predict(embeddings) - - def fit_peptides(self, peptides: list[LinearPeptide], rts: np.ndarray): - embeddings = np.stack( - [self.embedder.embed(peptide) for peptide in peptides], axis=0 - ) - assert embeddings.shape[0] == len(peptides) - assert embeddings.shape[1] == FEATURES - assert len(embeddings.shape) == 2 - self.fit(embeddings, rts) - - def fit_stripped_sequence( - self, peptides: list[str], masses: list[float], rts: list[float], **kwargs - ): - embeddings = np.stack( - [ - self.embedder.embed_stripped_sequence(peptide, mass) - for peptide, mass in zip(peptides, masses, strict=True) - ], - axis=0, - ) - assert embeddings.shape[0] == len(peptides) - assert embeddings.shape[1] == FEATURES - assert len(embeddings.shape) == 2 - self.fit(embeddings, np.array(rts), **kwargs) - - -def default_rt_model() -> SimpleRTModel: - model = SimpleRTModel( - weights=np.array([ - -26.36735781, - 0.0, - -53.56934945, - -55.21925322, - 178.27425793, - -66.75431489, - -209.06799874, - 125.83324634, - -150.87179673, - 148.44146624, - 63.25018127, - -91.4846459, - -17.6613223, - -83.61554405, - -196.30278774, - -68.4594332, - -50.41868969, - 50.06798268, - 187.24671262, - 20.80340359, - -486.74342897, - 0.0, - -433.18876836, - -468.34916512, - -527.09505792, - -448.14074773, - -467.64202839, - -531.7258505, - -499.37551192, - -538.98567687, - -493.35713573, - -440.11155882, - -466.97560832, - -473.17200096, - -462.76910048, - -454.20171251, - -472.42193567, - -516.75912556, - -521.42637442, - -488.67922573, - 2.08151486, - 0.0, - -3.84035216, - 0.75315676, - 9.55433749, - 20.95398651, - 40.11952704, - -10.65939963, - -18.59012557, - -1.93303538, - 22.55617111, - 14.19808588, - 12.09946731, - 13.13722242, - 29.0289534, - 12.82120924, - 10.56736659, - -1.14792772, - -7.66936585, - 37.4589648, - 328.94046503, - -567.69946649, - ]), - bias=np.float64(-567.6994664944469), - ) - - return model diff --git a/python/speclib_builder/speclib_builder/onxx_predictor.py b/python/speclib_builder/speclib_builder/onxx_predictor.py deleted file mode 100644 index fce37e2..0000000 --- a/python/speclib_builder/speclib_builder/onxx_predictor.py +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import annotations - -try: - from elfragmentadonnx.model import OnnxPeptideTransformer -except ImportError: - print("Could not import the OnnxPeptideTransformer, ML prediction will not work") - OnnxPeptideTransformer = None - -import re -from dataclasses import dataclass -from typing import Generator, Iterable - -from .base import MzIntPair, PeptideElement -from .builder import EntryElements, PeptideAnnotator -from .ml.simplertmodel import SimpleRTModel, default_rt_model - -MOD_REGEX = re.compile(r"(\[[A-Z:0-9]*?\])") - - -@dataclass(slots=True) -class OnnxPeptideTransformerAnnotator(PeptideAnnotator): - inference_model: OnnxPeptideTransformer - rt_model: SimpleRTModel - min_ordinal: int - max_ordinal: int - min_intensity: float - num_yielded: int = 0 - max_tokens: int = 30 - - def model(self, elem: PeptideElement) -> EntryElements: - return list(self.batched_model([elem]))[0] - - def filter_tokenizable( - self, elems: Iterable[PeptideElement] - ) -> list[PeptideElement]: - out = [] - for elem in elems: - if (token_count := self.count_tokens(elem.peptide)) > self.max_tokens: - print(f"Skipping {elem.peptide} because it has {token_count} tokens") - else: - out.append(elem) - return out - - def yielding_adapter( - self, elems: Iterable[PeptideElement] - ) -> Generator[tuple[str, int, float] | None, None, None]: - for elem in elems: - yield elem.peptide, elem.charge, elem.nce - - @staticmethod - def count_tokens(peptide: str) -> int: - # Pretty heuristic for now ... seems to work fine and fast enough - num_mods = peptide.count("[") - stripped_peptide = MOD_REGEX.sub("", peptide).strip("-") - return num_mods + len(stripped_peptide) + 2 - - def batched_model( - self, elements: list[PeptideElement] - ) -> Generator[EntryElements, None, None]: - elems = self.filter_tokenizable(elements) - - for pe, elem in zip( - elems, - self.inference_model.predict_batched_annotated( - self.yielding_adapter(elems), - min_intensity=self.min_intensity, - min_ordinal=self.min_ordinal, - max_ordinal=self.max_ordinal, - ), - strict=True, - ): - if elem is None: - continue - pep, ion_dict = elem - ion_dict = { - k: MzIntPair(mz=v[0], intensity=v[1]) for k, v in ion_dict.items() - } - try: - rt_seconds = self.rt_model.predict_peptide(pep) - except ValueError as e: - if "Invalid residue U" in str(e): - print(f"Skipping peptide {pep} due to invalid residue U") - continue - yield EntryElements( - peptide=pep, - rt_seconds=rt_seconds, - ion_dict=ion_dict, - decoy=pe.decoy, - id=self.num_yielded, - decoy_group=pe.decoy_group, - ) - self.num_yielded += 1 - - @staticmethod - def get_default() -> "OnnxPeptideTransformerAnnotator": - if OnnxPeptideTransformer is None: - raise ImportError( - "Could not import the OnnxPeptideTransformer, ML prediction will not work" - ) - rt_model = default_rt_model() - return OnnxPeptideTransformerAnnotator( - inference_model=OnnxPeptideTransformer.default_model(), - rt_model=rt_model, - min_ordinal=2, - max_ordinal=1000, - min_intensity=0.001, - ) diff --git a/python/speclib_builder/speclib_builder/writer.py b/python/speclib_builder/speclib_builder/writer.py deleted file mode 100644 index d5fc5bb..0000000 --- a/python/speclib_builder/speclib_builder/writer.py +++ /dev/null @@ -1,144 +0,0 @@ -import io -import json -from dataclasses import dataclass, field -from pathlib import Path -from typing import List, Dict, Any -import time - -from loguru import logger -import msgpack -import zstandard as zstd - -from speclib_builder.base import SpeclibElement - - -@dataclass -class FormatWriter: - """Individual format writer""" - - path: Path - format_name: str - _handle: Any = None - _num_written: int = 0 - _write_time: float = 0.0 - - def open(self): - self.path.parent.mkdir(parents=True, exist_ok=True) - - # TODO: This is super ugly ... make this a protocol... - if self.format_name == "ndjson": - warn_extension(self.path, "ndjson") - self._handle = open(self.path, "w") - elif self.format_name == "ndjson_zstd": - warn_extension(self.path, "ndjson.zst") - self._handle = zstd.open(self.path, "wt", encoding="utf-8") - elif self.format_name == "msgpack": - warn_extension(self.path, "msgpack") - self._handle = open(self.path, "wb") - elif self.format_name == "msgpack_zstd": - warn_extension(self.path, "msgpack.zst") - self._handle = zstd.open(self.path, "wb") - else: - raise ValueError(f"Unknown format: {self.format_name}") - - def append(self, data: SpeclibElement): - start_time = time.time() - data_dict = data.model_dump() - - if self.format_name == "ndjson": - json.dump(data_dict, self._handle) - self._handle.write("\n") - - elif self.format_name == "ndjson_zstd": - json.dump(data_dict, self._handle) - self._handle.write("\n") - - elif self.format_name == "msgpack": - packed = msgpack.packb(data_dict, use_bin_type=True) - self._handle.write(packed) - - elif self.format_name == "msgpack_zstd": - packed = msgpack.packb(data_dict, use_bin_type=True) - self._handle.write(packed) - - self._write_time += time.time() - start_time - self._num_written += 1 - - def close(self): - self._handle.close() - - def get_stats(self) -> Dict: - file_size = self.path.stat().st_size if self.path.exists() else 0 - return { - "format": self.format_name, - "path": str(self.path), - "num_written": self._num_written, - "write_time": self._write_time, - "file_size": file_size, - "avg_time_per_record": self._write_time / max(1, self._num_written), - } - - -@dataclass -class SpeclibWriter: - path: Path - # format: str = "ndjson" - file_format: str = "msgpack_zstd" - _opened: bool = False - _num_written: int = 0 - _writers: List[FormatWriter] = field(default_factory=list) - - def __post_init__(self): - if isinstance(self.path, str): - self.path = Path(self.path) - - if not isinstance(self.path, Path): - raise TypeError( - f"Expected path to be a string or Path object, got {type(self.path)}" - ) - - self._writer = FormatWriter( - path=self.path, - format_name=self.file_format, - ) - - def append(self, data: SpeclibElement): - if not self._opened: - raise RuntimeError("Writer is not open") - - if self._writer is None: - raise RuntimeError( - "Unregistered handle. Make sure you are using this as a context manager" - ) - - self._num_written += 1 - self._writer.append(data) - - def __enter__(self): - self.path.parent.mkdir(parents=True, exist_ok=True) - self._writer.open() - self._opened = True - self._num_written = 0 - - return self - - def __exit__(self, exc_type, exc_value, traceback): - self._opened = False - self._writer.close() - - stats = self._writer.get_stats() - logger.debug( - f"Finished writing {stats['num_written']} records to {stats['path']} " - f"in {stats['write_time']:.3f}s " - f"({stats['avg_time_per_record'] * 1000:.2f}ms per record)" - ) - - -def warn_extension(path: Path, ext: str): - """Warn if the file extension is not as expected.""" - if not path.name.endswith(f".{ext}"): - logger.warning( - f"Warning: File {path} does not have the expected .{ext} extension. " - "This may cause issues with some tools." - ) - time.sleep(5) # Small delay to ensure the warning is visible in logs diff --git a/python/speclib_builder/tests/test_decoys.py b/python/speclib_builder/tests/test_decoys.py deleted file mode 100644 index 460bc88..0000000 --- a/python/speclib_builder/tests/test_decoys.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from speclib_builder.decoys import ( - DecoyStrategy, - as_decoy, - as_decoy_proforma, -) - -SAMPLES = [ - ( - "MYPEPTIDEK", - { - DecoyStrategy.REVERSE: "MEDITPEPYK", - DecoyStrategy.MUTATE: "LSLDLSVEDL", - DecoyStrategy.EDGE_MUTATE: "MSPEPTIDDK", - }, - ), - ( - "MYPEPTIDEK/2", - { - DecoyStrategy.REVERSE: "MEDITPEPYK/2", - DecoyStrategy.MUTATE: "LSLDLSVEDL/2", - DecoyStrategy.EDGE_MUTATE: "MSPEPTIDDK/2", - }, - ), - ( - "MYPEPT[U:21]IDEK", - { - DecoyStrategy.REVERSE: "MEDIT[U:21]PEPYK", - DecoyStrategy.MUTATE: "LSLDLSVEDL", - DecoyStrategy.EDGE_MUTATE: "MSPEPT[U:21]IDDK", - }, - ), -] - - -@pytest.mark.parametrize("peptide, expected", SAMPLES) -def test_as_decoy(peptide, expected): - for strat, expect_seq in expected.items(): - assert as_decoy(peptide, strat) == expect_seq, f"{peptide}, {strat}" - assert as_decoy_proforma(peptide, strat) == expect_seq, f"{peptide}, {strat}" diff --git a/python/speclib_builder/tests/test_entry_matches_rust.py b/python/speclib_builder/tests/test_entry_matches_rust.py deleted file mode 100644 index 34ceea2..0000000 --- a/python/speclib_builder/tests/test_entry_matches_rust.py +++ /dev/null @@ -1,142 +0,0 @@ -import json -import subprocess -from pathlib import Path - -import pytest -from speclib_builder.base import ElutionGroup, SpeclibElement -from speclib_builder.decoys import DecoyStrategy -from speclib_builder.fasta_cli import ( - DummyAnnotator, - OnnxPeptideTransformerAnnotator, - _main, -) - - -# @pytest.fixture -# def rust_json(): -# # Calls the rust side to generate a json serialized version -# # of the library file, we will use that to compare -# # against the python generated version (so if we can serialize-deserialize) -# # and get the same result, we are good. -# args = [ -# "cargo", -# "run", -# "--bin", -# "timsseek_sample_speclib", -# "sample", -# ] -# -# outs = subprocess.run(args, check=True, capture_output=True) -# _json_sample = json.loads(outs.stdout) -# # files = {k.stem: k for k in list(Path(tmpdir).rglob("*.json"))} -# yield outs.stdout -# -# -# def test_speclib_ser(rust_json): -# speclib_list = json.loads(rust_json) -# # Will raise a validation error if the -# # python model does not match the example from the rust model. -# _data = ElutionGroup(**speclib_list[0]["elution_group"]) -# _data = SpeclibElement(**speclib_list[0]) -# pass - - -@pytest.mark.parametrize( - "decoy_strategy", - [ - DecoyStrategy.REVERSE, - DecoyStrategy.MUTATE, - DecoyStrategy.EDGE_MUTATE, - ], -) -@pytest.mark.parametrize( - "annotator", - [ - DummyAnnotator, - OnnxPeptideTransformerAnnotator, - ], -) -@pytest.mark.parametrize( - "extension_format_pair", - [ - pytest.param(("ndjson", "ndjson"), id="ndj"), - pytest.param( - ("ndjson_zstd", "ndjson.zst"), - id="ndj_zs", - ), - pytest.param( - ("msgpack", "msgpack"), - id="msp", - ), - pytest.param( - ("msgpack_zstd", "msgpack.zst"), - id="msp_zs", - ), - ], -) -def test_speclib_deser(tmpdir, annotator, decoy_strategy, extension_format_pair): - fasta_path = Path(tmpdir) / "test.fasta" - outfile = Path(tmpdir) / ("test." + extension_format_pair[1]) - fasta_contents = [ - ">sp|P62805|H4_HUMAN Histone H4 OS=Homo sapiens OX=9606 GN=H4C1 PE=1 SV=2", - "MSGRGKGGKGLGKGGAKRHRKVLRDNIQGITKPAIRRLARRGGVKRISGLIYEETRGVLK", - "VFLENVIRDAVTYTEHAKRKTVTAMDVVYALKRQGRTLYGFGG", - ] - with open(fasta_path, "w") as f: - f.writelines([f"{x}\n" for x in fasta_contents]) - - if hasattr(annotator, "get_default"): - ann = annotator.get_default() - else: - ann = annotator() - - _main( - fasta_file=str(fasta_path), - outfile=str(outfile), - max_keep=20, - decoy_strategy=decoy_strategy, - annotator=ann, - file_format=extension_format_pair[0], - ) - - parse_results = _read_speclib_rust(str(outfile)) - assert parse_results["returncode"] == 0 - - # Making flow 1 direction - # json_contents = json.loads(parse_results["stdout"]) - # if decoy_strategy == DecoyStrategy.EDGE_MUTATE: - # assert len(json_contents) == 57 - # elif decoy_strategy == DecoyStrategy.MUTATE: - # assert len(json_contents) == 55 - # elif decoy_strategy == DecoyStrategy.REVERSE: - # assert len(json_contents) == 56 - - # for elem in json_contents: - # # Make sure it passes validation - # _ = SpeclibElement(**elem) - - -def _read_speclib_rust(file): - # Calls the rust side to generate a json serialized version - # of the library file, we will use that to compare - # against the python generated version (so if we can serialize-deserialize) - # and get the same result, we are good. - args = [ - "cargo", - "run", - "--bin", - "timsseek_sample_speclib", - "parse", - "-s", - str(file), - ] - - outs = subprocess.run(args, check=False, capture_output=True) - if outs.returncode != 0: - print(outs.stderr.decode("utf-8")) - raise RuntimeError(f"Failed to parse speclib file: {file}") - return { - "stdout": outs.stdout.decode("utf-8"), - "stderr": outs.stderr.decode("utf-8"), - "returncode": outs.returncode, - } diff --git a/python/speclib_builder/tests/test_isotopes.py b/python/speclib_builder/tests/test_isotopes.py deleted file mode 100644 index 2c6bea8..0000000 --- a/python/speclib_builder/tests/test_isotopes.py +++ /dev/null @@ -1,17 +0,0 @@ -from speclib_builder.isotopes import peptide_isotopes - - -def test_peptide_isotopes(): - """ - Test function to verify peptide isotope calculations. - """ - iso = peptide_isotopes(60, 5) - expected = [0.3972, 0.2824, 0.1869, 0.0846] - expected = [val / 0.3972 for val in expected[:3]] # Normalize first 3 values - - # Check if all differences are within tolerance - tolerance = 0.02 - matched = all(abs(a - b) <= tolerance for a, b in zip(iso, expected, strict=True)) - - assert matched, f"Test failed: {iso} != {expected}" - print("Test passed successfully!") diff --git a/python/speclib_builder/tests/test_peptide_swap.py b/python/speclib_builder/tests/test_peptide_swap.py deleted file mode 100644 index c29ca8b..0000000 --- a/python/speclib_builder/tests/test_peptide_swap.py +++ /dev/null @@ -1,272 +0,0 @@ -import copy - -from speclib_builder.base import ( - ElutionGroup, - PrecursorEntry, - SpeclibElement, -) -from speclib_builder.decoys import DecoyStrategy - - -def _sample_element(): - entry = { - "precursor": { - "sequence": "SIIQSAQQDSIKK/2", - "charge": 2, - "decoy": 0, - "decoy_group": 0, - }, - "elution_group": { - "id": 4, - "mobility": 1.0175, - "rt_seconds": 0, - "precursor_labels": [ - -1, - 1, - 2, - 3, - ], - "precursor_mz": 722.8980675419999, - "precursor_charge": 2, - "fragment_mzs": [ - 1004.5382, - 623.34436, - 1132.5968, - 917.50616, - 1245.6808, - 566.80231, - 314.20798, - 475.32495, - 846.46906, - 718.41046, - 590.35193, - 442.26657, - ], - "fragment_labels": [ - "y9^1", - "y11^2", - "y10^1", - "y8^1", - "y11^1", - "y10^2", - "b3^1", - "y4^1", - "y7^1", - "y6^1", - "y5^1", - "b4^1", - ], - "precursor_intensities": [ - 0.001, - 1.0, - 0.6709999999999999, - 0.22512049999999995, - ], - "fragment_intensities": [ - 1.0, - 0.72318065, - 0.50104403, - 0.32450068, - 0.29743919, - 0.23268574, - 0.20915699, - 0.18289551, - 0.17597583, - 0.14646909, - 0.11264818, - 0.060880262, - ], - }, - } - - elem = SpeclibElement(**entry) - - return elem - - -def test_peptide_swap_same(): - elem = _sample_element() - elem_bkp = copy.deepcopy(elem) - new_elem = elem.swap_peptide( - proforma=elem.precursor.sequence, - decoy=False, - id=0, - ) - assert elem.precursor == new_elem.precursor - assert new_elem.elution_group.id == 0 - - # Make sure no modification in-place has happened - assert elem_bkp.elution_group.id == 4 - assert elem.elution_group.id == 4 - - assert elem.elution_group.precursor_mz == new_elem.elution_group.precursor_mz - assert ( - elem.elution_group.precursor_labels == new_elem.elution_group.precursor_labels - ) - - assert set(x for x in elem.elution_group.fragment_labels) == set( - x for x in new_elem.elution_group.fragment_labels - ) - orig_dict = { - x[0]: x[1] - for x in zip( - elem.elution_group.fragment_labels, elem.elution_group.fragment_mzs - ) - } - new_dict = { - x[0]: x[1] - for x in zip( - new_elem.elution_group.fragment_labels, new_elem.elution_group.fragment_mzs - ) - } - for k in elem.elution_group.fragment_labels: - orig = orig_dict[k] - new = new_dict[k] - diff = orig - new - - assert abs(diff) < 0.002, f"{k}: {diff}; original: {orig}, new: {new}" - - -def test_peptide_swap_decoy(): - elem = _sample_element() - new_pep = elem.precursor.sequence.replace("I", "A") - new_elem = elem.swap_peptide( - proforma=new_pep, - decoy=True, - id=0, - ) - assert new_elem.precursor.decoy - assert new_elem.precursor.sequence == new_pep - assert new_elem.precursor.sequence != elem.precursor.sequence - - assert set(elem.elution_group.fragment_labels) == set( - new_elem.elution_group.fragment_labels - ) - sames = {} - diffs = {} - - orig_dict = { - x[0]: x[1] - for x in zip( - elem.elution_group.fragment_labels, elem.elution_group.fragment_mzs - ) - } - new_dict = { - x[0]: x[1] - for x in zip( - new_elem.elution_group.fragment_labels, new_elem.elution_group.fragment_mzs - ) - } - for i, x in enumerate(elem.elution_group.fragment_labels): - orig = orig_dict[x] - new = new_dict[x] - diff = orig - new - - if abs(diff) < 0.002: - sames[x] = diff - else: - diffs[x] = diff - - assert len(sames) == 0 - V_TO_A_MASS = 42.04807294523107 - expect_one_diff = { - "y9^1", - "y11^2", - "y10^1", - "y8^1", - "y4^1", - "y7^1", - "y6^1", - "y5^1", - } - expect_two_diff = { - "y11^1", - "b3^1", - "b4^1", - } - expect_half_diff = { - "y10^2", - } - - for k in expect_one_diff: - assert k in diffs - assert (abs(diffs[k]) - V_TO_A_MASS) < 0.002 - - for k in expect_two_diff: - assert k in diffs - assert (abs(diffs[k]) - (2 * V_TO_A_MASS)) < 0.002 - - for k in expect_half_diff: - assert k in diffs - assert (abs(diffs[k]) - (V_TO_A_MASS / 2)) < 0.002 - - -def test_peptide_decoy_pseudorev(): - elem = _sample_element() - new_elem = elem.generate_massshift_decoy(id=0, decoy_strategy=DecoyStrategy.REVERSE) - assert new_elem.precursor.decoy - assert new_elem.precursor.sequence != elem.precursor.sequence - assert new_elem.precursor.sequence == "SKISDQQASQIIK/2" - - # http://db.systemsbiology.net:8080/proteomicsToolkit/FragIonServlet - expect = [ - ( - 1445.79587, - "y13^1", - ), - ( - 1358.76385, - "y12^1", - ), - ( - 1230.66888, - "y11^1", - ), - ( - 1117.58482, - "y10^1", - ), - ( - 1030.55279, - "y9^1", - ), - ( - 915.52585, - "y8^1", - ), - ( - 787.46727, - "y7^1", - ), - ( - 659.40869, - "y6^1", - ), - ( - 588.37158, - "y5^1", - ), - ( - 501.33955, - "y4^1", - ), - ( - 373.28098, - "y3^1", - ), - ( - 260.19691, - "y2^1", - ), - ( - 147.11285, - "y1^1", - ), - ] - - for mz, k in expect: - # Note that here I am using the original element to check - # for the presence of the key. BUT the new one for the mass. - if k in elem.elution_group.fragment_labels: - idx = new_elem.elution_group.fragment_labels.index(k) - assert abs(new_elem.elution_group.fragment_mzs[idx] - mz) < 0.01 diff --git a/python/timsquery_pyo3/pyproject.toml b/python/timsquery_pyo3/pyproject.toml index 5b51b13..4ccb076 100644 --- a/python/timsquery_pyo3/pyproject.toml +++ b/python/timsquery_pyo3/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "timsquery_pyo3" -version = "0.27.0" +version = "0.28.0" requires-python = ">=3.11,<3.14" classifiers = [ "Programming Language :: Rust", diff --git a/run.bash b/run.bash index c0ed38d..868d2cf 100644 --- a/run.bash +++ b/run.bash @@ -64,12 +64,11 @@ if [ -f "$SPECLIB_NAME" ]; then else echo "Building speclib" sleep 2 - uv run speclib_build_fasta \ - --fasta_file $FASTA_FILE \ - --decoy_strategy REVERSE \ - --max_ions 10 \ - --outfile $SPECLIB_NAME \ - --model onnx + cargo run --release -p speclib_build_cli -- \ + --fasta $FASTA_FILE \ + --fixed-mod "C[U:4]" \ + --max-ions 10 \ + -o $SPECLIB_NAME fi cargo run --release --bin timsseek -- \ diff --git a/rust/speclib_build_cli/Cargo.toml b/rust/speclib_build_cli/Cargo.toml new file mode 100644 index 0000000..3d30f60 --- /dev/null +++ b/rust/speclib_build_cli/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "speclib_build_cli" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +name = "speclib_build_cli" +path = "src/lib.rs" + +[[bin]] +name = "speclib_build" +path = "src/main.rs" + +[dependencies] +timsseek = { path = "../timsseek" } +micromzpaf = { path = "../micromzpaf" } +rustyms = { workspace = true } + +clap = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +indicatif = { workspace = true } + +reqwest = { version = "0.12", features = ["json"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +toml = "0.8" +bloomfilter = "1" + +[dev-dependencies] +tempfile = { workspace = true } diff --git a/rust/speclib_build_cli/src/cli.rs b/rust/speclib_build_cli/src/cli.rs new file mode 100644 index 0000000..336a5d6 --- /dev/null +++ b/rust/speclib_build_cli/src/cli.rs @@ -0,0 +1,116 @@ +use clap::Parser; +use std::path::PathBuf; + +/// Build a spectral library from FASTA or peptide list using Koina predictions. +#[derive(Debug, Parser)] +#[command(name = "speclib_build", version, about)] +pub struct Cli { + // ── Input ────────────────────────────────────────────────────────────── + /// FASTA file to digest into peptides. + #[arg(long)] + pub fasta: Option, + + /// Pre-digested peptide list (one bare sequence per line). + #[arg(long)] + pub peptide_list: Option, + + // ── Config ───────────────────────────────────────────────────────────── + /// TOML config file; CLI flags override values from this file. + #[arg(long)] + pub config: Option, + + // ── Output ───────────────────────────────────────────────────────────── + /// Output path for the spectral library (default: library.msgpack.zst). + #[arg(long, short = 'o')] + pub output: Option, + + // ── Modifications ────────────────────────────────────────────────────── + /// Fixed modification, e.g. `Carbamidomethyl@C`. Repeatable. + #[arg(long = "fixed-mod")] + pub fixed_mods: Vec, + + /// Variable modification, e.g. `Oxidation@M`. Repeatable. + #[arg(long = "var-mod")] + pub var_mods: Vec, + + /// Maximum number of variable modifications per peptide. + #[arg(long)] + pub max_var_mods: Option, + + // ── Digestion ────────────────────────────────────────────────────────── + /// Minimum peptide length (amino acids). + #[arg(long)] + pub min_length: Option, + + /// Maximum peptide length (amino acids). + #[arg(long)] + pub max_length: Option, + + /// Maximum missed cleavages allowed. + #[arg(long)] + pub missed_cleavages: Option, + + // ── Charges ──────────────────────────────────────────────────────────── + /// Minimum precursor charge state. + #[arg(long)] + pub min_charge: Option, + + /// Maximum precursor charge state. + #[arg(long)] + pub max_charge: Option, + + // ── Decoys ───────────────────────────────────────────────────────────── + /// Decoy generation strategy: none | reverse | edge_mutate. + #[arg(long)] + pub decoy_strategy: Option, + + // ── Prediction ───────────────────────────────────────────────────────── + /// Koina fragment intensity model name. + #[arg(long)] + pub fragment_model: Option, + + /// Koina retention-time model name. + #[arg(long)] + pub rt_model: Option, + + /// Base URL for the Koina prediction service. + #[arg(long)] + pub koina_url: Option, + + /// Number of peptides per Koina request batch. + #[arg(long)] + pub batch_size: Option, + + /// Normalised collision energy (0.0–1.0). + #[arg(long)] + pub nce: Option, + + /// Delay in milliseconds between Koina request batches (rate-limit friendly). + #[arg(long)] + pub request_delay_ms: Option, + + // ── Fragment filters ─────────────────────────────────────────────────── + /// Maximum number of fragment ions retained per precursor. + #[arg(long)] + pub max_ions: Option, + + /// Minimum precursor m/z to include. + #[arg(long)] + pub min_mz: Option, + + /// Maximum precursor m/z to include. + #[arg(long)] + pub max_mz: Option, + + /// Minimum fragment ion m/z to retain. + #[arg(long)] + pub min_ion_mz: Option, + + /// Maximum fragment ion m/z to retain. + #[arg(long)] + pub max_ion_mz: Option, + + /// Minimum number of fragment ions required to keep a precursor. + #[arg(long)] + pub min_ions: Option, +} diff --git a/rust/speclib_build_cli/src/config.rs b/rust/speclib_build_cli/src/config.rs new file mode 100644 index 0000000..383a8de --- /dev/null +++ b/rust/speclib_build_cli/src/config.rs @@ -0,0 +1,403 @@ +use serde::Deserialize; +use std::path::PathBuf; + +use crate::cli::Cli; + +// ── Default value helpers ──────────────────────────────────────────────────── + +fn default_enzyme() -> String { + "trypsin".to_string() +} +fn default_min_length() -> usize { + 7 +} +fn default_max_length() -> usize { + 25 +} +fn default_missed_cleavages() -> usize { + 1 +} +fn default_max_variable_mods() -> usize { + 2 +} +fn default_min_charge() -> u8 { + 2 +} +fn default_max_charge() -> u8 { + 4 +} +fn default_decoy_strategy() -> String { + "none".to_string() +} +fn default_fragment_model() -> String { + "Prosit_2023_intensity_timsTOF".to_string() +} +fn default_rt_model() -> String { + "Prosit_2019_irt".to_string() +} +fn default_koina_url() -> String { + "https://koina.wilhelmlab.org/v2/models".to_string() +} +fn default_batch_size() -> usize { + 1000 +} +fn default_nce() -> f32 { + 0.3 +} +fn default_max_ions() -> usize { + 10 +} +fn default_min_mz() -> f32 { + 400.0 +} +fn default_max_mz() -> f32 { + 2000.0 +} +fn default_min_ion_mz() -> f32 { + 250.0 +} +fn default_max_ion_mz() -> f32 { + 2000.0 +} +fn default_min_ions() -> usize { + 3 +} +fn default_output() -> PathBuf { + PathBuf::from("library.msgpack.zst") +} + +// ── Sub-structs ────────────────────────────────────────────────────────────── + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct DigestionConfig { + /// Enzyme name (currently only "trypsin" is recognised). + pub enzyme: String, + pub min_length: usize, + pub max_length: usize, + pub missed_cleavages: usize, +} + +impl Default for DigestionConfig { + fn default() -> Self { + Self { + enzyme: default_enzyme(), + min_length: default_min_length(), + max_length: default_max_length(), + missed_cleavages: default_missed_cleavages(), + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct ModificationsConfig { + /// Fixed modifications applied to every matching residue, e.g. ["Carbamidomethyl@C"]. + pub fixed: Vec, + /// Variable modifications considered during peptide generation, e.g. ["Oxidation@M"]. + pub variable: Vec, + /// Maximum number of variable modifications per peptide. + pub max_variable: usize, +} + +impl Default for ModificationsConfig { + fn default() -> Self { + Self { + fixed: Vec::new(), + variable: Vec::new(), + max_variable: default_max_variable_mods(), + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct ChargesConfig { + pub min: u8, + pub max: u8, +} + +impl Default for ChargesConfig { + fn default() -> Self { + Self { + min: default_min_charge(), + max: default_max_charge(), + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct DecoysConfig { + /// Strategy for generating decoy entries: "none", "reverse", or "edge_mutate". + pub strategy: String, +} + +impl Default for DecoysConfig { + fn default() -> Self { + Self { + strategy: default_decoy_strategy(), + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct PredictionConfig { + pub fragment_model: String, + pub rt_model: String, + pub koina_url: String, + pub batch_size: usize, + /// Normalised collision energy sent to the Koina model (0.0–1.0). + pub nce: f32, + /// Delay in milliseconds between Koina request batches (0 = no delay). + pub request_delay_ms: u64, +} + +impl Default for PredictionConfig { + fn default() -> Self { + Self { + fragment_model: default_fragment_model(), + rt_model: default_rt_model(), + koina_url: default_koina_url(), + batch_size: default_batch_size(), + nce: default_nce(), + request_delay_ms: 0, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct FiltersConfig { + pub max_ions: usize, + pub min_mz: f32, + pub max_mz: f32, + pub min_ion_mz: f32, + pub max_ion_mz: f32, + /// Discard precursors with fewer than this many fragment ions surviving filters. + pub min_ions: usize, +} + +impl Default for FiltersConfig { + fn default() -> Self { + Self { + max_ions: default_max_ions(), + min_mz: default_min_mz(), + max_mz: default_max_mz(), + min_ion_mz: default_min_ion_mz(), + max_ion_mz: default_max_ion_mz(), + min_ions: default_min_ions(), + } + } +} + +// ── Top-level config ───────────────────────────────────────────────────────── + +#[derive(Debug, Deserialize)] +#[serde(default)] +pub struct SpeclibBuildConfig { + // Inputs — not directly deserialised from TOML but set after merging CLI args. + #[serde(skip)] + pub fasta: Option, + #[serde(skip)] + pub peptide_list: Option, + + pub output: PathBuf, + pub digestion: DigestionConfig, + pub modifications: ModificationsConfig, + pub charges: ChargesConfig, + pub decoys: DecoysConfig, + pub prediction: PredictionConfig, + pub filters: FiltersConfig, +} + +impl Default for SpeclibBuildConfig { + fn default() -> Self { + Self { + fasta: None, + peptide_list: None, + output: default_output(), + digestion: DigestionConfig::default(), + modifications: ModificationsConfig::default(), + charges: ChargesConfig::default(), + decoys: DecoysConfig::default(), + prediction: PredictionConfig::default(), + filters: FiltersConfig::default(), + } + } +} + +impl SpeclibBuildConfig { + /// Build a config by loading an optional TOML file and then overlaying CLI args. + /// + /// Precedence: CLI flags > TOML file > compiled-in defaults. + pub fn from_cli(cli: &Cli) -> Result> { + // Start from TOML file if provided, otherwise use defaults. + let mut cfg: SpeclibBuildConfig = if let Some(path) = &cli.config { + let raw = std::fs::read_to_string(path) + .map_err(|e| format!("cannot read config file {}: {e}", path.display()))?; + toml::from_str(&raw) + .map_err(|e| format!("invalid TOML in {}: {e}", path.display()))? + } else { + SpeclibBuildConfig::default() + }; + + // ── Inputs ── + cfg.fasta = cli.fasta.clone(); + cfg.peptide_list = cli.peptide_list.clone(); + + // ── Output ── + if let Some(v) = &cli.output { + cfg.output = v.clone(); + } + + // ── Digestion ── + if let Some(v) = cli.min_length { + cfg.digestion.min_length = v; + } + if let Some(v) = cli.max_length { + cfg.digestion.max_length = v; + } + if let Some(v) = cli.missed_cleavages { + cfg.digestion.missed_cleavages = v; + } + + // ── Modifications ── + if !cli.fixed_mods.is_empty() { + cfg.modifications.fixed = cli.fixed_mods.clone(); + } + if !cli.var_mods.is_empty() { + cfg.modifications.variable = cli.var_mods.clone(); + } + if let Some(v) = cli.max_var_mods { + cfg.modifications.max_variable = v; + } + + // ── Charges ── + if let Some(v) = cli.min_charge { + cfg.charges.min = v; + } + if let Some(v) = cli.max_charge { + cfg.charges.max = v; + } + + // ── Decoys ── + if let Some(ref v) = cli.decoy_strategy { + cfg.decoys.strategy = v.clone(); + } + + // ── Prediction ── + if let Some(ref v) = cli.fragment_model { + cfg.prediction.fragment_model = v.clone(); + } + if let Some(ref v) = cli.rt_model { + cfg.prediction.rt_model = v.clone(); + } + if let Some(ref v) = cli.koina_url { + cfg.prediction.koina_url = v.clone(); + } + if let Some(v) = cli.batch_size { + cfg.prediction.batch_size = v; + } + if let Some(v) = cli.nce { + cfg.prediction.nce = v; + } + if let Some(v) = cli.request_delay_ms { + cfg.prediction.request_delay_ms = v; + } + + // ── Filters ── + if let Some(v) = cli.max_ions { + cfg.filters.max_ions = v; + } + if let Some(v) = cli.min_mz { + cfg.filters.min_mz = v; + } + if let Some(v) = cli.max_mz { + cfg.filters.max_mz = v; + } + if let Some(v) = cli.min_ion_mz { + cfg.filters.min_ion_mz = v; + } + if let Some(v) = cli.max_ion_mz { + cfg.filters.max_ion_mz = v; + } + if let Some(v) = cli.min_ions { + cfg.filters.min_ions = v; + } + + Ok(cfg) + } + + /// Validate logical constraints across the merged config. + pub fn validate(&self) -> Result<(), String> { + // Exactly one input source must be provided. + match (&self.fasta, &self.peptide_list) { + (None, None) => { + return Err( + "provide exactly one input: --fasta or --peptide-list".to_string() + ) + } + (Some(_), Some(_)) => { + return Err( + "--fasta and --peptide-list are mutually exclusive".to_string() + ) + } + _ => {} + } + + if self.digestion.min_length == 0 { + return Err("min_length must be >= 1".to_string()); + } + if self.digestion.max_length < self.digestion.min_length { + return Err(format!( + "max_length ({}) must be >= min_length ({})", + self.digestion.max_length, self.digestion.min_length + )); + } + if self.charges.max < self.charges.min { + return Err(format!( + "max_charge ({}) must be >= min_charge ({})", + self.charges.max, self.charges.min + )); + } + let valid_strategies = ["none", "reverse", "edge_mutate"]; + if !valid_strategies.contains(&self.decoys.strategy.as_str()) { + return Err(format!( + "unknown decoy strategy {:?}; valid values: {}", + self.decoys.strategy, + valid_strategies.join(", ") + )); + } + if !(0.0..=1.0).contains(&self.prediction.nce) { + return Err(format!( + "nce ({}) must be in [0.0, 1.0]", + self.prediction.nce + )); + } + if self.filters.min_ions == 0 { + return Err("min_ions must be >= 1".to_string()); + } + if self.filters.max_ions < self.filters.min_ions { + return Err(format!( + "max_ions ({}) must be >= min_ions ({})", + self.filters.max_ions, self.filters.min_ions + )); + } + if self.filters.min_mz >= self.filters.max_mz { + return Err(format!( + "min_mz ({}) must be < max_mz ({})", + self.filters.min_mz, self.filters.max_mz + )); + } + if self.filters.min_ion_mz >= self.filters.max_ion_mz { + return Err(format!( + "min_ion_mz ({}) must be < max_ion_mz ({})", + self.filters.min_ion_mz, self.filters.max_ion_mz + )); + } + + Ok(()) + } +} diff --git a/rust/speclib_build_cli/src/decoys.rs b/rust/speclib_build_cli/src/decoys.rs new file mode 100644 index 0000000..3509c87 --- /dev/null +++ b/rust/speclib_build_cli/src/decoys.rs @@ -0,0 +1,147 @@ +use std::collections::HashMap; +use std::sync::LazyLock; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecoyMode { + None, + Reverse, + EdgeMutate, +} + +impl DecoyMode { + pub fn from_str(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "reverse" => Ok(Self::Reverse), + "edge_mutate" => Ok(Self::EdgeMutate), + _ => Err(format!("Unknown decoy strategy: {s}")), + } + } +} + +/// Substitution table ported from Python: +/// "GAVLIFMPWSCTYHKRQEND" → "LLLVVLLLLTSSSSLLNDQE" +pub static MUTATE_TABLE: LazyLock> = LazyLock::new(|| { + let from = "GAVLIFMPWSCTYHKRQEND"; + let to = "LLLVVLLLLTSSSSLLNDQE"; + from.chars().zip(to.chars()).collect() +}); + +/// Keep first and last AA, reverse the middle. +/// "PEPTIDEK" → "PEDITPEK" +/// Sequences of <= 2 chars are returned as-is. +pub fn reverse_decoy(seq: &str) -> String { + if seq.len() <= 2 { + return seq.to_string(); + } + let chars: Vec = seq.chars().collect(); + let first = chars[0]; + let last = *chars.last().unwrap(); + let middle: String = chars[1..chars.len() - 1].iter().rev().collect(); + format!("{first}{middle}{last}") +} + +/// Mutate positions 1 and -2 using MUTATE_TABLE. +/// Keep first (0) and last (-1) unchanged. +/// Sequences of <= 3 chars are returned as-is. +pub fn edge_mutate(seq: &str) -> String { + if seq.len() <= 3 { + return seq.to_string(); + } + let mut chars: Vec = seq.chars().collect(); + let n = chars.len(); + chars[1] = *MUTATE_TABLE.get(&chars[1]).unwrap_or(&chars[1]); + chars[n - 2] = *MUTATE_TABLE.get(&chars[n - 2]).unwrap_or(&chars[n - 2]); + chars.into_iter().collect() +} + +/// Dispatch decoy generation. Panics if called with DecoyMode::None. +pub fn generate_decoy(seq: &str, mode: DecoyMode) -> String { + match mode { + DecoyMode::None => panic!("generate_decoy called with DecoyMode::None"), + DecoyMode::Reverse => reverse_decoy(seq), + DecoyMode::EdgeMutate => edge_mutate(seq), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reverse_matches_python() { + assert_eq!(reverse_decoy("PEPTIDEK"), "PEDITPEK"); + assert_eq!(reverse_decoy("PEPTIDEPINK"), "PNIPEDITPEK"); + } + + #[test] + fn test_edge_mutate() { + let result = edge_mutate("PEPTIDEK"); + assert_eq!(result.chars().nth(0).unwrap(), 'P'); // first preserved + assert_ne!(result.chars().nth(1).unwrap(), 'E'); // mutated + assert_eq!(result.chars().last().unwrap(), 'K'); // last preserved + // E maps to D in MUTATE_TABLE (from "GAVLIFMPWSCTYHKRQEND" → "LLLVVLLLLTSSSSLLNDQE", + // position 17: E→D), so position 1 should be 'D' + assert_eq!(result.chars().nth(1).unwrap(), 'D'); + } + + #[test] + fn test_mutate_table_completeness() { + for aa in "GAVLIFMPWSCTYHKRQEND".chars() { + assert!(MUTATE_TABLE.contains_key(&aa), "Missing mapping for {aa}"); + } + } + + #[test] + fn test_generate_decoy_reverse() { + assert_eq!(generate_decoy("PEPTIDEK", DecoyMode::Reverse), "PEDITPEK"); + } + + #[test] + fn test_generate_decoy_edge_mutate() { + let result = generate_decoy("PEPTIDEK", DecoyMode::EdgeMutate); + assert_ne!(result, "PEPTIDEK"); + assert_eq!(result.len(), "PEPTIDEK".len()); + } + + #[test] + fn test_reverse_short_sequences() { + assert_eq!(reverse_decoy(""), ""); + assert_eq!(reverse_decoy("A"), "A"); + assert_eq!(reverse_decoy("AK"), "AK"); + assert_eq!(reverse_decoy("AEK"), "AEK"); // 3 chars: first=A, middle=E reversed=E, last=K + } + + #[test] + fn test_edge_mutate_short_sequences() { + assert_eq!(edge_mutate(""), ""); + assert_eq!(edge_mutate("A"), "A"); + assert_eq!(edge_mutate("AK"), "AK"); + assert_eq!(edge_mutate("AEK"), "AEK"); + } + + #[test] + fn test_decoy_mode_from_str() { + assert_eq!(DecoyMode::from_str("none").unwrap(), DecoyMode::None); + assert_eq!(DecoyMode::from_str("reverse").unwrap(), DecoyMode::Reverse); + assert_eq!( + DecoyMode::from_str("edge_mutate").unwrap(), + DecoyMode::EdgeMutate + ); + assert!(DecoyMode::from_str("bogus").is_err()); + } + + #[test] + #[should_panic(expected = "generate_decoy called with DecoyMode::None")] + fn test_generate_decoy_none_panics() { + generate_decoy("PEPTIDEK", DecoyMode::None); + } + + #[test] + fn test_reverse_three_chars() { + // "AEK": first=A, middle=[E] reversed=[E], last=K → "AEK" + assert_eq!(reverse_decoy("AEK"), "AEK"); + // "AEPK": first=A, middle=[E,P] reversed=[P,E], last=K → "APEK" + assert_eq!(reverse_decoy("AEPK"), "APEK"); + } +} diff --git a/rust/speclib_build_cli/src/dedup.rs b/rust/speclib_build_cli/src/dedup.rs new file mode 100644 index 0000000..f33f43d --- /dev/null +++ b/rust/speclib_build_cli/src/dedup.rs @@ -0,0 +1,105 @@ +use bloomfilter::Bloom; +use std::collections::HashMap; +use timsseek::DigestSlice; + +pub struct PeptideDedup; + +impl PeptideDedup { + /// Deduplicate DigestSlices using bloom filter fast-pass + bucket HashMap. + /// `estimated_count` used to preallocate bloom + HashMap capacity. + pub fn dedup(slices: Vec, estimated_count: usize) -> Vec { + let est = estimated_count.max(64); + let mut bloom = Bloom::new_for_fp_rate(est, 0.01); + let mut buckets: HashMap<(u8, u16), Vec> = HashMap::with_capacity(est / 20); + + for slice in slices { + let seq = slice.as_str().as_bytes(); + let key = (seq[0], seq.len() as u16); + + if !bloom.check(seq) { + // Definitely new + bloom.set(seq); + buckets.entry(key).or_default().push(slice); + } else { + // Maybe seen — check bucket + let bucket = buckets.entry(key).or_default(); + if !bucket.iter().any(|existing| existing.as_str().as_bytes() == seq) { + bucket.push(slice); + } + } + } + + buckets.into_values().flat_map(|v| v.into_iter()).collect() + } + + /// Estimate unique peptide count from protein lengths for preallocation. + pub fn estimate_from_proteins(total_aa: usize, missed_cleavages: usize) -> usize { + (total_aa / 10) * (1 + missed_cleavages) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + use timsseek::models::DecoyMarking; + + fn make_slice(seq: &str) -> DigestSlice { + let s: Arc = seq.into(); + let len = s.len() as u16; + DigestSlice::new(s, 0..len, DecoyMarking::Target, 0) + } + + #[test] + fn test_dedup_removes_duplicates() { + // 5 slices, 2 dupes → 3 unique + let slices = vec![ + make_slice("PEPTIDE"), + make_slice("LAGER"), + make_slice("PEPTIDE"), // dupe + make_slice("TOMATO"), + make_slice("LAGER"), // dupe + ]; + let result = PeptideDedup::dedup(slices, 10); + assert_eq!(result.len(), 3); + // All three unique sequences are present + let seqs: Vec<&str> = result.iter().map(|s| s.as_str()).collect(); + assert!(seqs.contains(&"PEPTIDE")); + assert!(seqs.contains(&"LAGER")); + assert!(seqs.contains(&"TOMATO")); + } + + #[test] + fn test_dedup_same_len_different_seq() { + // Same length, different content → both kept + let slices = vec![ + make_slice("ABCDE"), + make_slice("VWXYZ"), + ]; + let result = PeptideDedup::dedup(slices, 10); + assert_eq!(result.len(), 2); + let seqs: Vec<&str> = result.iter().map(|s| s.as_str()).collect(); + assert!(seqs.contains(&"ABCDE")); + assert!(seqs.contains(&"VWXYZ")); + } + + #[test] + fn test_dedup_shared_protein_backbone() { + // Two slices from same Arc, same range → deduped + let backbone: Arc = "PEPTIDEPINKTOMATO".into(); + let len = backbone.len() as u16; + let slice1 = DigestSlice::new(backbone.clone(), 0..len, DecoyMarking::Target, 0); + let slice2 = DigestSlice::new(backbone.clone(), 0..len, DecoyMarking::Target, 0); + let slices = vec![slice1, slice2]; + let result = PeptideDedup::dedup(slices, 10); + assert_eq!(result.len(), 1); + assert_eq!(result[0].as_str(), "PEPTIDEPINKTOMATO"); + } + + #[test] + fn test_dedup_empty() { + // Empty input → empty output + let result = PeptideDedup::dedup(vec![], 0); + assert!(result.is_empty()); + } +} diff --git a/rust/speclib_build_cli/src/entry.rs b/rust/speclib_build_cli/src/entry.rs new file mode 100644 index 0000000..388061d --- /dev/null +++ b/rust/speclib_build_cli/src/entry.rs @@ -0,0 +1,323 @@ +use timsseek::IonAnnot; +use timsseek::data_sources::speclib::{PrecursorEntry, ReferenceEG, SerSpeclibElement}; +use timsseek::fragment_mass::elution_group_converter::{ + count_carbon_sulphur_in_sequence, supersimpleprediction, +}; +use timsseek::isotopes::peptide_isotopes; + +use crate::koina::models::{FragmentPrediction, RtPrediction}; + +// ── Filters ────────────────────────────────────────────────────────────────── + +pub struct EntryFilters { + pub max_ions: usize, + pub min_mz: f32, + pub max_mz: f32, + pub min_ion_mz: f32, + pub max_ion_mz: f32, + pub min_ions: usize, +} + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +/// Strip bracket-enclosed modifications from a sequence. +/// +/// "PEPTC[U:4]IDEK" → "PEPTCIDEK" +pub fn strip_mods(seq: &str) -> String { + let mut out = String::with_capacity(seq.len()); + let mut depth = 0usize; + for ch in seq.chars() { + match ch { + '[' => depth += 1, + ']' => { + depth = depth.saturating_sub(1); + } + _ if depth == 0 => out.push(ch), + _ => {} + } + } + out +} + +/// Convert our short mod notation to ProForma for rustyms. +/// `C[U:4]` → `C[UNIMOD:4]`, mass-shift mods like `[+57.02]` pass through. +/// Note: rustyms parses `[U:N]` as element Uranium, not UNIMOD — must expand. +fn to_proforma(seq: &str) -> String { + seq.replace("[U:", "[UNIMOD:") +} + +/// Compute the monoisotopic precursor m/z using rustyms. +/// Input should be the modified sequence (mods included in mass). +fn compute_precursor_mz(modified_seq: &str, charge: u8) -> Option { + use rustyms::prelude::*; + let proforma = to_proforma(modified_seq); + let peptide = Peptidoform::pro_forma(&proforma, None).ok()?; + let linear = peptide.as_linear()?; + let formulas = linear.formulas(); + if formulas.is_empty() { + return None; + } + let mass = formulas[0].monoisotopic_mass().value; + let proton_mass = 1.007_276_f64; + Some((mass + proton_mass * charge as f64) / charge as f64) +} + +/// Count carbon and sulphur from modified sequence (mods affect formula). +fn count_cs_modified(modified_seq: &str) -> Option<(u16, u16)> { + let proforma = to_proforma(modified_seq); + count_carbon_sulphur_in_sequence(&proforma).ok() +} + +// ── Public API ──────────────────────────────────────────────────────────────── + +/// Convert Koina predictions + metadata into a [`SerSpeclibElement`]. +/// +/// Returns `None` when: +/// - Precursor m/z falls outside `[filters.min_mz, filters.max_mz]`. +/// - Fewer than `filters.min_ions` fragment ions survive the ion m/z filter. +/// - Mass computation fails (malformed sequence). +pub fn build_entry( + sequence: &str, + charge: u8, + decoy: bool, + decoy_group: u32, + id: u32, + fragment: &FragmentPrediction, + rt: &RtPrediction, + filters: &EntryFilters, +) -> Option { + // 1. Carbon / sulphur count from modified sequence (includes mod contributions). + let (ncarbon, nsulphur) = count_cs_modified(sequence)?; + let iso = peptide_isotopes(ncarbon, nsulphur); + + // 2. Precursor m/z from modified sequence (includes mod masses). + let precursor_mz = compute_precursor_mz(sequence, charge)?; + + // 4. Filter by precursor m/z range. + if precursor_mz < filters.min_mz as f64 || precursor_mz > filters.max_mz as f64 { + return None; + } + + // 5. Ion mobility prediction. + let mobility = supersimpleprediction(precursor_mz, charge as i32) as f32; + + // 6. Filter fragments: keep those within ion m/z bounds. + let min_ion = filters.min_ion_mz as f64; + let max_ion = filters.max_ion_mz as f64; + + // Collect (mz, intensity, annotation_str) triples that pass the m/z filter. + let mut passing: Vec<(f64, f32, &str)> = fragment + .mzs + .iter() + .zip(fragment.intensities.iter()) + .zip(fragment.annotations.iter()) + .filter(|((mz, _), _)| **mz >= min_ion && **mz <= max_ion) + .map(|((mz, intensity), ann)| (*mz, *intensity, ann.as_str())) + .collect(); + + // 7. Sort by intensity descending, truncate to max_ions. + passing.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); + passing.truncate(filters.max_ions); + + // 8. Require minimum number of surviving fragments. + if passing.len() < filters.min_ions { + return None; + } + + // 9. Parse ion annotation strings; skip unparseable. + let mut fragment_mzs: Vec = Vec::with_capacity(passing.len()); + let mut fragment_intensities: Vec = Vec::with_capacity(passing.len()); + let mut fragment_labels: Vec = Vec::with_capacity(passing.len()); + + for (mz, intensity, ann_str) in passing { + match IonAnnot::try_from(ann_str) { + Ok(ion) => { + fragment_mzs.push(mz); + fragment_intensities.push(intensity); + fragment_labels.push(ion); + } + Err(_) => { + tracing::debug!("Skipping unparseable ion annotation: {ann_str}"); + } + } + } + + // After dropping unparseable annotations the count may fall below min_ions. + if fragment_labels.len() < filters.min_ions { + return None; + } + + // 10. Build precursor labels and intensities from the isotope distribution. + let precursor_labels: Vec = vec![0i8, 1i8, 2i8]; + let precursor_intensities: Vec = vec![iso[0], iso[1], iso[2]]; + + // 11. Assemble the element. + let precursor = PrecursorEntry::new(sequence.to_owned(), charge, decoy, decoy_group); + let elution_group = ReferenceEG::new( + id, + precursor_mz, + precursor_labels, + fragment_mzs, + fragment_labels, + precursor_intensities, + fragment_intensities, + mobility, + rt.irt, + ); + + Some(SerSpeclibElement::new(precursor, elution_group)) +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use crate::koina::models::{FragmentPrediction, RtPrediction}; + + fn make_filters(min_ions: usize) -> EntryFilters { + EntryFilters { + max_ions: 10, + min_mz: 400.0, + max_mz: 2000.0, + min_ion_mz: 150.0, + max_ion_mz: 2000.0, + min_ions, + } + } + + fn four_ion_fragment() -> FragmentPrediction { + FragmentPrediction { + annotations: vec![ + "y3^1".to_string(), + "y4^1".to_string(), + "b3^1".to_string(), + "b4^1".to_string(), + ], + mzs: vec![400.0, 500.0, 300.0, 350.0], + intensities: vec![0.9, 0.8, 0.7, 0.6], + } + } + + #[test] + fn test_strip_mods() { + assert_eq!(strip_mods("PEPTC[U:4]IDEK"), "PEPTCIDEK"); + assert_eq!(strip_mods("PEPTM[+15.995]IDEK"), "PEPTMIDEK"); + assert_eq!(strip_mods("PEPTIDEK"), "PEPTIDEK"); + } + + #[test] + fn test_compute_precursor_mz() { + let mz = compute_precursor_mz("PEPTIDEK", 2).unwrap(); + assert!(mz > 400.0 && mz < 600.0, "Expected reasonable m/z, got {mz}"); + } + + #[test] + fn test_precursor_mz_includes_mod_mass() { + // to_proforma converts [U:4] → [UNIMOD:4] before rustyms + let mz_unmod = compute_precursor_mz("PEPTCIDEK", 2).unwrap(); + let mz_mod = compute_precursor_mz("PEPTC[U:4]IDEK", 2).unwrap(); + let diff = mz_mod - mz_unmod; + // Cys carbamidomethyl = +57.02 Da, at charge 2 = +28.51 m/z + assert!( + (diff - 28.51).abs() < 0.1, + "Mod should add ~28.51 m/z at z=2, got diff={diff}" + ); + } + + + #[test] + fn test_build_entry_basic() { + let fragment = four_ion_fragment(); + let rt = RtPrediction { irt: 30.0 }; + let filters = make_filters(3); + + let result = build_entry( + "PEPTIDEK", + 2, + false, + 0, + 42, + &fragment, + &rt, + &filters, + ); + + assert!(result.is_some(), "Expected Some but got None"); + } + + #[test] + fn test_build_entry_skipped_too_few_ions() { + // Only 1 ion — min_ions = 3 → should return None. + let fragment = FragmentPrediction { + annotations: vec!["y3^1".to_string()], + mzs: vec![400.0], + intensities: vec![0.9], + }; + let rt = RtPrediction { irt: 30.0 }; + let filters = make_filters(3); + + let result = build_entry( + "PEPTIDEK", + 2, + false, + 0, + 1, + &fragment, + &rt, + &filters, + ); + + assert!(result.is_none(), "Expected None but got Some"); + } + + #[test] + fn test_build_entry_precursor_mz_filter() { + let fragment = four_ion_fragment(); + let rt = RtPrediction { irt: 30.0 }; + // Tight precursor window that excludes PEPTIDEK/2 (~476 Da) + let filters = EntryFilters { + max_ions: 10, + min_mz: 1800.0, + max_mz: 2000.0, + min_ion_mz: 150.0, + max_ion_mz: 2000.0, + min_ions: 3, + }; + + let result = build_entry( + "PEPTIDEK", + 2, + false, + 0, + 2, + &fragment, + &rt, + &filters, + ); + + assert!(result.is_none(), "Expected None due to precursor m/z filter"); + } + + #[test] + fn test_build_entry_decoy() { + let fragment = four_ion_fragment(); + let rt = RtPrediction { irt: 30.0 }; + let filters = make_filters(3); + + let result = build_entry( + "KEDITREP", + 2, + true, + 99, + 7, + &fragment, + &rt, + &filters, + ); + + // Decoy peptide should still build an entry if the mz/ions pass filters + // (KEDITREP ~476 should pass default 400–2000 window). + assert!(result.is_some(), "Expected Some for decoy entry"); + } +} diff --git a/rust/speclib_build_cli/src/koina/adapter.rs b/rust/speclib_build_cli/src/koina/adapter.rs new file mode 100644 index 0000000..9435fac --- /dev/null +++ b/rust/speclib_build_cli/src/koina/adapter.rs @@ -0,0 +1,331 @@ +use crate::koina::models::{ + FragmentPrediction, KoinaRequest, KoinaTensor, KoinaTensorData, KoinaResponse, PredictionInput, + RtPrediction, +}; + +// ── Request builders ───────────────────────────────────────────────────────── + +/// Build a fragment-intensity inference request. +/// +/// Tensors sent: +/// - `peptide_sequences` (BYTES) +/// - `precursor_charges` (INT32) +/// - `collision_energies` (FP32) +pub fn build_fragment_request(inputs: &[PredictionInput], request_id: &str) -> KoinaRequest { + let n = inputs.len(); + let sequences: Vec = inputs.iter().map(|i| i.sequence.clone()).collect(); + let charges: Vec = inputs.iter().map(|i| i.charge as i32).collect(); + let nces: Vec = inputs.iter().map(|i| i.nce).collect(); + + KoinaRequest { + id: request_id.to_string(), + inputs: vec![ + KoinaTensor { + name: "peptide_sequences".to_string(), + shape: vec![n, 1], + datatype: "BYTES".to_string(), + data: KoinaTensorData::Strings(sequences), + }, + KoinaTensor { + name: "precursor_charges".to_string(), + shape: vec![n, 1], + datatype: "INT32".to_string(), + data: KoinaTensorData::Ints(charges), + }, + KoinaTensor { + name: "collision_energies".to_string(), + shape: vec![n, 1], + datatype: "FP32".to_string(), + data: KoinaTensorData::Floats(nces), + }, + ], + } +} + +/// Build an RT inference request. +/// +/// Tensor sent: +/// - `peptide_sequences` (BYTES) +pub fn build_rt_request(inputs: &[PredictionInput], request_id: &str) -> KoinaRequest { + let n = inputs.len(); + let sequences: Vec = inputs.iter().map(|i| i.sequence.clone()).collect(); + + KoinaRequest { + id: request_id.to_string(), + inputs: vec![KoinaTensor { + name: "peptide_sequences".to_string(), + shape: vec![n, 1], + datatype: "BYTES".to_string(), + data: KoinaTensorData::Strings(sequences), + }], + } +} + +// ── Response parsers ───────────────────────────────────────────────────────── + +/// Parse a fragment-intensity Koina response into per-peptide predictions. +/// +/// Expects outputs named `annotation`, `mz`, and `intensities`. +/// Zero-intensity ions are filtered out from the returned predictions. +pub fn parse_fragment_response( + response: KoinaResponse, + batch_size: usize, +) -> Result, String> { + let annotations_tensor = find_output(&response, "annotation")?; + let mz_tensor = find_output(&response, "mz")?; + let intensities_tensor = find_output(&response, "intensities")?; + + // Each tensor has shape [batch_size, num_ions]; flatten then chunk. + let ions_per_peptide = total_len(annotations_tensor) / batch_size; + + // Koina returns annotations like "y1+1" (plus for charge), but + // IonAnnot expects "y1^1" (caret). Convert. + let annotations_flat: Vec = annotations_tensor + .data + .iter() + .map(|v| { + let s = v.as_str().unwrap_or(""); + koina_annotation_to_mzpaf(s) + }) + .collect(); + let mzs_flat: Vec = mz_tensor + .data + .iter() + .map(|v| v.as_f64().unwrap_or(0.0)) + .collect(); + let intensities_flat: Vec = intensities_tensor + .data + .iter() + .map(|v| v.as_f64().unwrap_or(0.0) as f32) + .collect(); + + let mut predictions = Vec::with_capacity(batch_size); + for i in 0..batch_size { + let start = i * ions_per_peptide; + let end = start + ions_per_peptide; + + let mut anns = Vec::new(); + let mut mzs = Vec::new(); + let mut ints = Vec::new(); + + for j in start..end { + let intensity = intensities_flat[j]; + if intensity > 0.0 { + anns.push(annotations_flat[j].clone()); + mzs.push(mzs_flat[j]); + ints.push(intensity); + } + } + + predictions.push(FragmentPrediction { + annotations: anns, + mzs, + intensities: ints, + }); + } + + Ok(predictions) +} + +/// Parse an RT Koina response into per-peptide iRT predictions. +/// +/// Expects an output named `irt`. +pub fn parse_rt_response( + response: KoinaResponse, + batch_size: usize, +) -> Result, String> { + let irt_tensor = find_output(&response, "irt")?; + + if irt_tensor.data.len() != batch_size { + return Err(format!( + "irt tensor has {} entries, expected {batch_size}", + irt_tensor.data.len() + )); + } + + let predictions = irt_tensor + .data + .iter() + .map(|v| RtPrediction { + irt: v.as_f64().unwrap_or(0.0) as f32, + }) + .collect(); + + Ok(predictions) +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +fn find_output<'a>( + response: &'a KoinaResponse, + name: &str, +) -> Result<&'a crate::koina::models::KoinaOutputTensor, String> { + response + .outputs + .iter() + .find(|o| o.name == name) + .ok_or_else(|| format!("Koina response missing output tensor {name:?}")) +} + +fn total_len(tensor: &crate::koina::models::KoinaOutputTensor) -> usize { + tensor.data.len() +} + +/// Convert Koina annotation format to mzPAF format. +/// +/// Koina: "y1+1" (series, ordinal, '+', charge) +/// mzPAF: "y1^1" (series, ordinal, '^', charge) +fn koina_annotation_to_mzpaf(s: &str) -> String { + // Find the last '+' that separates ordinal from charge + // Annotations look like: y1+1, y1+2, y1+3, b12+2 + if let Some(pos) = s.rfind('+') { + let (prefix, charge) = s.split_at(pos); + format!("{prefix}^{}", &charge[1..]) + } else { + s.to_string() + } +} + +// ── Tests ──────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use crate::koina::models::{KoinaOutputTensor, KoinaResponse, KoinaTensorData, PredictionInput}; + + fn sample_inputs() -> Vec { + vec![ + PredictionInput { + sequence: "PEPTIDE".to_string(), + charge: 2, + nce: 0.3, + }, + PredictionInput { + sequence: "LAGER".to_string(), + charge: 3, + nce: 0.25, + }, + ] + } + + #[test] + fn test_build_fragment_request() { + let inputs = sample_inputs(); + let req = build_fragment_request(&inputs, "req-1"); + + assert_eq!(req.id, "req-1"); + assert_eq!(req.inputs.len(), 3); + + let seq_tensor = &req.inputs[0]; + assert_eq!(seq_tensor.name, "peptide_sequences"); + assert_eq!(seq_tensor.datatype, "BYTES"); + assert_eq!(seq_tensor.shape, vec![2, 1]); + if let KoinaTensorData::Strings(ref seqs) = seq_tensor.data { + assert_eq!(seqs, &["PEPTIDE", "LAGER"]); + } else { + panic!("expected Strings data"); + } + + let charge_tensor = &req.inputs[1]; + assert_eq!(charge_tensor.name, "precursor_charges"); + assert_eq!(charge_tensor.datatype, "INT32"); + assert_eq!(charge_tensor.shape, vec![2, 1]); + if let KoinaTensorData::Ints(ref charges) = charge_tensor.data { + assert_eq!(charges, &[2, 3]); + } else { + panic!("expected Ints data"); + } + + let nce_tensor = &req.inputs[2]; + assert_eq!(nce_tensor.name, "collision_energies"); + assert_eq!(nce_tensor.datatype, "FP32"); + assert_eq!(nce_tensor.shape, vec![2, 1]); + if let KoinaTensorData::Floats(ref nces) = nce_tensor.data { + assert!((nces[0] - 0.3).abs() < 1e-6); + assert!((nces[1] - 0.25).abs() < 1e-6); + } else { + panic!("expected Floats data"); + } + } + + #[test] + fn test_build_rt_request() { + let inputs = sample_inputs(); + let req = build_rt_request(&inputs, "req-rt-1"); + + assert_eq!(req.id, "req-rt-1"); + assert_eq!(req.inputs.len(), 1); + + let seq_tensor = &req.inputs[0]; + assert_eq!(seq_tensor.name, "peptide_sequences"); + assert_eq!(seq_tensor.datatype, "BYTES"); + assert_eq!(seq_tensor.shape, vec![2, 1]); + if let KoinaTensorData::Strings(ref seqs) = seq_tensor.data { + assert_eq!(seqs, &["PEPTIDE", "LAGER"]); + } else { + panic!("expected Strings data"); + } + } + + #[test] + fn test_parse_fragment_response_filters_zeros() { + // 2 peptides, 3 ions each (flat: 6 entries per tensor) + // Peptide 0: ions [1.0, 0.0, 0.5] → only 2 survive + // Peptide 1: ions [0.0, 0.0, 0.8] → only 1 survives + let annotations_data: Vec = vec![ + "y1^1", "b2^1", "y3^1", "y1^1", "b2^1", "y3^1", + ] + .into_iter() + .map(|s| serde_json::Value::String(s.to_string())) + .collect(); + + let mz_data: Vec = vec![100.0_f64, 200.0, 300.0, 110.0, 210.0, 310.0] + .into_iter() + .map(serde_json::Value::from) + .collect(); + + let intensity_data: Vec = + vec![1.0_f64, 0.0, 0.5, 0.0, 0.0, 0.8] + .into_iter() + .map(serde_json::Value::from) + .collect(); + + let response = KoinaResponse { + id: "test".to_string(), + outputs: vec![ + KoinaOutputTensor { + name: "annotation".to_string(), + datatype: "BYTES".to_string(), + shape: vec![2, 3], + data: annotations_data, + }, + KoinaOutputTensor { + name: "mz".to_string(), + datatype: "FP32".to_string(), + shape: vec![2, 3], + data: mz_data, + }, + KoinaOutputTensor { + name: "intensities".to_string(), + datatype: "FP32".to_string(), + shape: vec![2, 3], + data: intensity_data, + }, + ], + }; + + let preds = parse_fragment_response(response, 2).unwrap(); + assert_eq!(preds.len(), 2); + + // Peptide 0: ions at index 0 (1.0) and 2 (0.5) survive; index 1 (0.0) is dropped. + assert_eq!(preds[0].intensities.len(), 2); + assert_eq!(preds[0].annotations, vec!["y1^1", "y3^1"]); + assert!((preds[0].mzs[0] - 100.0).abs() < 1e-6); + assert!((preds[0].mzs[1] - 300.0).abs() < 1e-6); + + // Peptide 1: only index 2 (0.8) survives. + assert_eq!(preds[1].intensities.len(), 1); + assert_eq!(preds[1].annotations, vec!["y3^1"]); + assert!((preds[1].mzs[0] - 310.0).abs() < 1e-6); + } +} diff --git a/rust/speclib_build_cli/src/koina/mod.rs b/rust/speclib_build_cli/src/koina/mod.rs new file mode 100644 index 0000000..c8b2876 --- /dev/null +++ b/rust/speclib_build_cli/src/koina/mod.rs @@ -0,0 +1,114 @@ +pub mod adapter; +pub mod models; + +use models::{ + FragmentModel, FragmentPrediction, KoinaRequest, KoinaResponse, PredictionInput, RtModel, + RtPrediction, +}; + +// ── Client ─────────────────────────────────────────────────────────────────── + +pub struct KoinaClient { + http: reqwest::Client, + base_url: String, + fragment_model: FragmentModel, + rt_model: RtModel, +} + +impl KoinaClient { + pub fn new(base_url: impl Into, fragment_model: FragmentModel, rt_model: RtModel) -> Self { + Self { + http: reqwest::Client::new(), + base_url: base_url.into(), + fragment_model, + rt_model, + } + } + + /// POST fragment-intensity predictions for `inputs`. + /// + /// The batch is sent as a single request; callers are responsible for + /// splitting into appropriately sized batches before calling this method. + pub async fn predict_fragments( + &self, + inputs: &[PredictionInput], + ) -> Result, String> { + let url = format!( + "{}/{}/infer", + self.base_url, + self.fragment_model.model_name() + ); + let request = adapter::build_fragment_request(inputs, "fragment-0"); + let response = self.send_with_retry(&url, &request).await?; + adapter::parse_fragment_response(response, inputs.len()) + } + + /// POST RT predictions for `inputs`. + pub async fn predict_rt( + &self, + inputs: &[PredictionInput], + ) -> Result, String> { + let url = format!("{}/{}/infer", self.base_url, self.rt_model.model_name()); + let request = adapter::build_rt_request(inputs, "rt-0"); + let response = self.send_with_retry(&url, &request).await?; + adapter::parse_rt_response(response, inputs.len()) + } + + /// Send a request with up to 3 attempts and exponential back-off. + /// + /// 4xx responses are not retried (client error, retrying will not help). + async fn send_with_retry( + &self, + url: &str, + request: &KoinaRequest, + ) -> Result { + const MAX_ATTEMPTS: u32 = 3; + + let mut last_err = String::new(); + + for attempt in 0..MAX_ATTEMPTS { + if attempt > 0 { + let delay = std::time::Duration::from_millis(200 * (1u64 << attempt)); + tokio::time::sleep(delay).await; + } + + let resp = self + .http + .post(url) + .json(request) + .send() + .await + .map_err(|e| e.to_string())?; + + let status = resp.status(); + + // Client errors (4xx) — no point retrying. + if status.is_client_error() { + let body = resp.text().await.unwrap_or_default(); + return Err(format!( + "Koina request failed with {status} (not retrying): {body}" + )); + } + + if status.is_success() { + let body = resp.text().await.map_err(|e| { + format!("failed to read Koina response body: {e}") + })?; + return serde_json::from_str::(&body).map_err(|e| { + // Show first 500 chars of body for debugging + let preview: String = body.chars().take(500).collect(); + format!( + "failed to deserialise Koina response: {e}\nbody preview: {preview}" + ) + }); + } + + // 5xx or other non-success — record and retry. + last_err = format!("Koina request failed with status {status}"); + } + + Err(format!( + "Koina request failed after {MAX_ATTEMPTS} attempts: {last_err}" + )) + } +} diff --git a/rust/speclib_build_cli/src/koina/models.rs b/rust/speclib_build_cli/src/koina/models.rs new file mode 100644 index 0000000..6b78282 --- /dev/null +++ b/rust/speclib_build_cli/src/koina/models.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; + +// ── Model registry ─────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FragmentModel { + Prosit2023IntensityTimstof, + Prosit2020IntensityHcd, + Prosit2020IntensityCid, + AlphaPeptDeepMs2Generic, +} + +impl FragmentModel { + pub fn from_name(name: &str) -> Result { + match name { + "Prosit_2023_intensity_timsTOF" => Ok(Self::Prosit2023IntensityTimstof), + "Prosit_2020_intensity_HCD" => Ok(Self::Prosit2020IntensityHcd), + "Prosit_2020_intensity_CID" => Ok(Self::Prosit2020IntensityCid), + "AlphaPeptDeep_ms2_generic" => Ok(Self::AlphaPeptDeepMs2Generic), + other => Err(format!( + "unknown fragment model {other:?}; valid: Prosit_2023_intensity_timsTOF, Prosit_2020_intensity_HCD, Prosit_2020_intensity_CID, AlphaPeptDeep_ms2_generic" + )), + } + } + + pub fn model_name(&self) -> &str { + match self { + Self::Prosit2023IntensityTimstof => "Prosit_2023_intensity_timsTOF", + Self::Prosit2020IntensityHcd => "Prosit_2020_intensity_HCD", + Self::Prosit2020IntensityCid => "Prosit_2020_intensity_CID", + Self::AlphaPeptDeepMs2Generic => "AlphaPeptDeep_ms2_generic", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RtModel { + Prosit2019Irt, + AlphaPeptDeepRtGeneric, +} + +impl RtModel { + pub fn from_name(name: &str) -> Result { + match name { + "Prosit_2019_irt" => Ok(Self::Prosit2019Irt), + "AlphaPeptDeep_rt_generic" => Ok(Self::AlphaPeptDeepRtGeneric), + other => Err(format!( + "unknown RT model {other:?}; valid: Prosit_2019_irt, AlphaPeptDeep_rt_generic" + )), + } + } + + pub fn model_name(&self) -> &str { + match self { + Self::Prosit2019Irt => "Prosit_2019_irt", + Self::AlphaPeptDeepRtGeneric => "AlphaPeptDeep_rt_generic", + } + } +} + +// ── Triton v2 wire types ───────────────────────────────────────────────────── + +#[derive(Debug, Serialize)] +pub struct KoinaRequest { + pub id: String, + pub inputs: Vec, +} + +#[derive(Debug, Serialize)] +pub struct KoinaTensor { + pub name: String, + pub shape: Vec, + pub datatype: String, + pub data: KoinaTensorData, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum KoinaTensorData { + Strings(Vec), + Ints(Vec), + Floats(Vec), +} + +#[derive(Debug, Deserialize)] +pub struct KoinaResponse { + pub id: String, + pub outputs: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct KoinaOutputTensor { + pub name: String, + pub datatype: String, + pub shape: Vec, + pub data: Vec, +} + +// ── Domain prediction types ────────────────────────────────────────────────── + +#[derive(Debug, Clone)] +pub struct PredictionInput { + pub sequence: String, + pub charge: u8, + pub nce: f32, +} + +#[derive(Debug, Clone)] +pub struct FragmentPrediction { + /// Ion annotations, e.g. "y3^1", "b5^2". + pub annotations: Vec, + pub mzs: Vec, + pub intensities: Vec, +} + +#[derive(Debug, Clone)] +pub struct RtPrediction { + pub irt: f32, +} diff --git a/rust/speclib_build_cli/src/lib.rs b/rust/speclib_build_cli/src/lib.rs new file mode 100644 index 0000000..bf40e8e --- /dev/null +++ b/rust/speclib_build_cli/src/lib.rs @@ -0,0 +1,8 @@ +pub mod cli; +pub mod config; +pub mod decoys; +pub mod dedup; +pub mod entry; +pub mod koina; +pub mod mods; +pub mod pipeline; diff --git a/rust/speclib_build_cli/src/main.rs b/rust/speclib_build_cli/src/main.rs new file mode 100644 index 0000000..7cadceb --- /dev/null +++ b/rust/speclib_build_cli/src/main.rs @@ -0,0 +1,33 @@ +use clap::Parser; +use speclib_build_cli::cli::Cli; +use speclib_build_cli::config::SpeclibBuildConfig; + +fn main() { + let cli = Cli::parse(); + + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "info".into()), + ) + .with_writer(std::io::stderr) + .init(); + + let config = match SpeclibBuildConfig::from_cli(&cli) { + Ok(c) => c, + Err(e) => { + eprintln!("Error loading config: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = config.validate() { + eprintln!("Invalid config: {e}"); + std::process::exit(1); + } + + let rt = tokio::runtime::Runtime::new().unwrap(); + if let Err(e) = rt.block_on(speclib_build_cli::pipeline::run(&config)) { + eprintln!("Pipeline error: {e}"); + std::process::exit(1); + } +} diff --git a/rust/speclib_build_cli/src/mods.rs b/rust/speclib_build_cli/src/mods.rs new file mode 100644 index 0000000..5c8e28a --- /dev/null +++ b/rust/speclib_build_cli/src/mods.rs @@ -0,0 +1,348 @@ +/// Proforma-like modification parsing and application for speclib_build. +/// +/// Supports notations like "C[U:4]", "M[U:35]", "S[U:21]", "M[+15.995]". +/// Fixed mods are inserted after every matching residue in the sequence. +/// Variable mods generate all combinations up to `max_mods` sites. + +// ── Types ──────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Modification { + /// Uppercase single-letter amino acid code. + pub target_residue: char, + /// Bracket notation including brackets, e.g. "[U:4]" or "[+15.995]". + pub notation: String, +} + +impl Modification { + /// Parse a modification string such as `"C[U:4]"` or `"M[+15.995]"`. + /// + /// The first character must be an uppercase ASCII letter (amino acid code). + /// The remainder must start with `[` and end with `]`. + pub fn parse(s: &str) -> Result { + let mut chars = s.chars(); + let first = chars + .next() + .ok_or_else(|| "modification string is empty".to_string())?; + + if !first.is_ascii_uppercase() { + return Err(format!( + "expected uppercase amino acid letter, got '{first}'" + )); + } + + let rest: String = chars.collect(); + if !rest.starts_with('[') || !rest.ends_with(']') { + return Err(format!( + "bracket notation must start with '[' and end with ']', got '{rest}'" + )); + } + if rest.len() < 3 { + // minimum: "[x]" + return Err(format!("bracket notation too short: '{rest}'")); + } + + Ok(Self { + target_residue: first, + notation: rest, + }) + } +} + +// ── Fixed modifications ─────────────────────────────────────────────────────── + +/// Insert each modification's notation after every matching unmodified residue. +/// +/// "Unmodified" means the residue is not already followed by a `[` bracket. +/// Fixed mods are applied left-to-right; each mod is applied in sequence +/// over the already-modified string (so you can stack multiple fixed mods). +pub fn apply_fixed_mods(sequence: &str, mods: &[Modification]) -> String { + let mut result = sequence.to_string(); + for m in mods { + result = apply_one_fixed_mod(&result, m); + } + result +} + +fn apply_one_fixed_mod(sequence: &str, m: &Modification) -> String { + let bytes = sequence.as_bytes(); + let target = m.target_residue as u8; + let mut out = String::with_capacity(sequence.len() + sequence.len() / 4); + + let mut i = 0; + while i < bytes.len() { + let b = bytes[i]; + out.push(b as char); + // Append notation only when: this byte matches the target AND the + // immediately following character is NOT '[' (already modified). + if b == target { + let already_modified = bytes.get(i + 1).copied() == Some(b'['); + if !already_modified { + out.push_str(&m.notation); + } + } + i += 1; + } + out +} + +// ── Variable modifications ──────────────────────────────────────────────────── + +/// Generate all forms of `sequence` with 0..=`max_mods` variable modifications +/// applied. +/// +/// Each element of `mods` specifies a residue and notation. The positions +/// considered are only unmodified sites (not already followed by `[`). +/// Returns at least the unmodified sequence (always the first element). +pub fn expand_variable_mods( + sequence: &str, + mods: &[Modification], + max_mods: usize, +) -> Vec { + // Collect all modifiable (residue_index_in_sequence, &Modification) pairs. + // We scan character-by-character, skipping bracket contents. + let positions = collect_modifiable_positions(sequence, mods); + + if positions.is_empty() || max_mods == 0 { + return vec![sequence.to_string()]; + } + + let cap = max_mods.min(positions.len()); + let mut results: Vec = Vec::new(); + + // k = 0 → unmodified form + results.push(sequence.to_string()); + + // k = 1 ..= cap + for k in 1..=cap { + let combos = combinations(&positions, k); + for combo in combos { + results.push(build_modified_sequence(sequence, &combo)); + } + } + + results +} + +/// A modifiable site: byte offset in `sequence` of the target residue and the +/// notation to insert after it. +#[derive(Clone)] +struct ModSite<'a> { + /// Byte offset of the residue character in the original sequence string. + byte_offset: usize, + notation: &'a str, +} + +/// Walk the sequence, honouring existing bracket groups, and collect all +/// positions where each mod could be applied. +fn collect_modifiable_positions<'a>( + sequence: &str, + mods: &'a [Modification], +) -> Vec> { + let mut sites: Vec> = Vec::new(); + let bytes = sequence.as_bytes(); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'[' { + // Skip over bracket group. + while i < bytes.len() && bytes[i] != b']' { + i += 1; + } + // skip ']' + i += 1; + continue; + } + let ch = bytes[i] as char; + // Check against each mod. + for m in mods { + if ch == m.target_residue { + // Make sure the next char is not '[' (already modified). + let already = bytes.get(i + 1).copied() == Some(b'['); + if !already { + sites.push(ModSite { + byte_offset: i, + notation: &m.notation, + }); + } + } + } + i += 1; + } + sites +} + +/// Reconstruct the sequence string with modifications inserted at the chosen +/// sites. Sites must be in ascending `byte_offset` order. +fn build_modified_sequence(sequence: &str, sites: &[&ModSite<'_>]) -> String { + let bytes = sequence.as_bytes(); + let mut out = String::with_capacity(sequence.len() + sites.len() * 8); + let mut prev = 0usize; + for site in sites { + // Copy up to and including the residue. + let end = site.byte_offset + 1; + out.push_str(&sequence[prev..end]); + out.push_str(site.notation); + prev = end; + } + // Remainder of sequence. + if prev < bytes.len() { + out.push_str(&sequence[prev..]); + } + out +} + +/// Generate all k-element combinations from `items`, preserving order. +fn combinations<'a>(items: &'a [ModSite<'a>], k: usize) -> Vec>> { + let mut result = Vec::new(); + let mut combo = Vec::with_capacity(k); + combinations_inner(items, k, 0, &mut combo, &mut result); + result +} + +fn combinations_inner<'a>( + items: &'a [ModSite<'a>], + k: usize, + start: usize, + combo: &mut Vec<&'a ModSite<'a>>, + result: &mut Vec>>, +) { + if combo.len() == k { + result.push(combo.clone()); + return; + } + let remaining = k - combo.len(); + for i in start..=(items.len().saturating_sub(remaining)) { + combo.push(&items[i]); + combinations_inner(items, k, i + 1, combo, result); + combo.pop(); + } +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + // ── Parsing ────────────────────────────────────────────────────────────── + + #[test] + fn test_parse_unimod_mod() { + let m = Modification::parse("C[U:4]").unwrap(); + assert_eq!(m.target_residue, 'C'); + assert_eq!(m.notation, "[U:4]"); + } + + #[test] + fn test_parse_mass_shift_mod() { + let m = Modification::parse("M[+15.995]").unwrap(); + assert_eq!(m.target_residue, 'M'); + assert_eq!(m.notation, "[+15.995]"); + } + + #[test] + fn test_parse_bad_first_char() { + assert!(Modification::parse("1[U:4]").is_err()); + assert!(Modification::parse("[U:4]").is_err()); + } + + #[test] + fn test_parse_bad_bracket() { + assert!(Modification::parse("CU:4]").is_err()); + assert!(Modification::parse("C[U:4").is_err()); + } + + // ── Fixed mods ─────────────────────────────────────────────────────────── + + #[test] + fn test_apply_fixed_mod() { + let m = Modification::parse("C[U:4]").unwrap(); + let result = apply_fixed_mods("PEPTCIDECK", &[m]); + assert_eq!(result, "PEPTC[U:4]IDEC[U:4]K"); + } + + #[test] + fn test_apply_fixed_mod_no_match() { + let m = Modification::parse("C[U:4]").unwrap(); + let result = apply_fixed_mods("PEPTIDEK", &[m]); + assert_eq!(result, "PEPTIDEK"); + } + + #[test] + fn test_apply_fixed_mod_does_not_double_modify() { + // If a residue is already bracketed it should not be modified again. + let m = Modification::parse("C[U:4]").unwrap(); + let already = "PEPTC[U:4]IDECK"; + let result = apply_fixed_mods(already, &[m]); + assert_eq!(result, "PEPTC[U:4]IDEC[U:4]K"); + } + + // ── Variable mods ──────────────────────────────────────────────────────── + + #[test] + fn test_expand_variable_mods_single() { + let m = Modification::parse("M[U:35]").unwrap(); + let results = expand_variable_mods("PEPTMIDMEK", &[m], 1); + assert!(results.contains(&"PEPTMIDMEK".to_string())); + assert!(results.contains(&"PEPTM[U:35]IDMEK".to_string())); + assert!(results.contains(&"PEPTMIDM[U:35]EK".to_string())); + // max_mods=1 → doubly-modified form should NOT be present + assert!(!results.contains(&"PEPTM[U:35]IDM[U:35]EK".to_string())); + assert_eq!(results.len(), 3); + } + + #[test] + fn test_expand_variable_mods_max_two() { + let m = Modification::parse("M[U:35]").unwrap(); + let results = expand_variable_mods("PEPTMIDMEK", &[m], 2); + assert!(results.contains(&"PEPTMIDMEK".to_string())); + assert!(results.contains(&"PEPTM[U:35]IDMEK".to_string())); + assert!(results.contains(&"PEPTMIDM[U:35]EK".to_string())); + assert!(results.contains(&"PEPTM[U:35]IDM[U:35]EK".to_string())); + assert_eq!(results.len(), 4); + } + + #[test] + fn test_expand_variable_mods_phospho_sty() { + // Multiple mod types: phospho on S and T. + let phospho_s = Modification::parse("S[U:21]").unwrap(); + let phospho_t = Modification::parse("T[U:21]").unwrap(); + // Sequence has one S and one T → positions: S@4, T@6 + let results = expand_variable_mods("PEPSTIEK", &[phospho_s, phospho_t], 1); + // Unmodified + assert!(results.contains(&"PEPSTIEK".to_string())); + // Only S modified + assert!(results.contains(&"PEPS[U:21]TIEK".to_string())); + // Only T modified + assert!(results.contains(&"PEPST[U:21]IEK".to_string())); + // max_mods=1 → doubly-modified absent + assert!(!results.contains(&"PEPS[U:21]T[U:21]IEK".to_string())); + assert_eq!(results.len(), 3); + } + + #[test] + fn test_expand_variable_mods_respects_fixed_mods() { + // Sequence already has a fixed C[U:4] — variable M should still work. + let var_m = Modification::parse("M[U:35]").unwrap(); + let sequence = "PEPTMC[U:4]IDMEK"; + let results = expand_variable_mods(sequence, &[var_m], 1); + assert!(results.contains(&"PEPTMC[U:4]IDMEK".to_string())); + assert!(results.contains(&"PEPTM[U:35]C[U:4]IDMEK".to_string())); + assert!(results.contains(&"PEPTMC[U:4]IDM[U:35]EK".to_string())); + assert_eq!(results.len(), 3); + } + + #[test] + fn test_expand_variable_mods_no_positions() { + let m = Modification::parse("M[U:35]").unwrap(); + let results = expand_variable_mods("PEPTIDEK", &[m], 2); + assert_eq!(results, vec!["PEPTIDEK".to_string()]); + } + + #[test] + fn test_expand_variable_mods_max_zero() { + let m = Modification::parse("M[U:35]").unwrap(); + let results = expand_variable_mods("PEPTMIDMEK", &[m], 0); + assert_eq!(results, vec!["PEPTMIDMEK".to_string()]); + } +} diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs new file mode 100644 index 0000000..ac4d1c6 --- /dev/null +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -0,0 +1,318 @@ +use std::io::BufRead; + +use indicatif::{ProgressBar, ProgressStyle}; +use timsseek::DigestSlice; +use timsseek::data_sources::speclib::SpeclibWriter; +use timsseek::digest::digestion::{DigestionEnd, DigestionParameters, DigestionPattern}; +use timsseek::protein::fasta::ProteinSequenceCollection; + +use crate::config::SpeclibBuildConfig; +use crate::decoys::{DecoyMode, generate_decoy}; +use crate::dedup::PeptideDedup; +use crate::entry::{EntryFilters, build_entry}; +use crate::koina::KoinaClient; +use crate::koina::models::{FragmentModel, PredictionInput, RtModel}; +use crate::mods::{Modification, apply_fixed_mods, expand_variable_mods}; + +// ── BatchItem ─────────────────────────────────────────────────────────────── + +struct BatchItem { + sequence: String, + charge: u8, + decoy: bool, + decoy_group: u32, +} + +// ── flush_batch ───────────────────────────────────────────────────────────── + +async fn flush_batch( + koina: &KoinaClient, + writer: &mut SpeclibWriter, + batch: &mut Vec, + filters: &EntryFilters, + nce: f32, + entry_id: &mut u32, + progress: &ProgressBar, +) -> Result<(), Box> { + if batch.is_empty() { + return Ok(()); + } + + // Koina/Prosit accepts UNIMOD notation — convert our short form [U:N] → [UNIMOD:N] + let inputs: Vec = batch + .iter() + .map(|item| PredictionInput { + sequence: item.sequence.replace("[U:", "[UNIMOD:"), + charge: item.charge, + nce, + }) + .collect(); + + let (fragments, rts) = tokio::try_join!( + async { koina.predict_fragments(&inputs).await.map_err(|e| -> Box { e.into() }) }, + async { koina.predict_rt(&inputs).await.map_err(|e| -> Box { e.into() }) }, + )?; + + for ((item, fragment), rt) in batch.drain(..).zip(fragments.iter()).zip(rts.iter()) { + if let Some(elem) = build_entry( + &item.sequence, + item.charge, + item.decoy, + item.decoy_group, + *entry_id, + fragment, + rt, + filters, + ) { + writer.append(&elem).map_err(|e| format!("write error: {e:?}"))?; + *entry_id += 1; + } + progress.inc(1); + } + + Ok(()) +} + +// ── run ───────────────────────────────────────────────────────────────────── + +pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box> { + // ── Phase 1: Get base peptides ────────────────────────────────────────── + + let base_peptides: Vec = if let Some(fasta_path) = &config.fasta { + tracing::info!("Reading FASTA: {}", fasta_path.display()); + let collection = ProteinSequenceCollection::from_fasta_file(fasta_path)?; + let total_aa: usize = collection.sequences.iter().map(|p| p.sequence.len()).sum(); + tracing::info!( + "Read {} proteins ({} amino acids)", + collection.sequences.len(), + total_aa, + ); + + let params = DigestionParameters { + min_length: config.digestion.min_length, + max_length: config.digestion.max_length, + pattern: DigestionPattern::trypsin(), + digestion_end: DigestionEnd::CTerm, + max_missed_cleavages: config.digestion.missed_cleavages, + }; + + // Warn about proteins containing non-standard amino acids + for prot in &collection.sequences { + if prot.sequence.chars().any(|c| matches!(c, 'U' | 'B' | 'J' | 'Z' | 'X')) { + tracing::warn!( + "Protein {} contains non-standard amino acids (U/B/J/Z/X) — affected peptides will be skipped", + prot.description, + ); + } + } + + let protein_seqs: Vec> = collection + .sequences + .iter() + .map(|p| p.sequence.clone()) + .collect(); + let raw = params.digest_multiple(&protein_seqs); + tracing::info!("Digested into {} raw peptides", raw.len()); + + let estimated = PeptideDedup::estimate_from_proteins(total_aa, config.digestion.missed_cleavages); + let deduped = PeptideDedup::dedup(raw, estimated); + let (deduped, skipped) = filter_nonstandard_aa(deduped); + if skipped > 0 { + tracing::warn!("Skipped {skipped} peptides containing non-standard amino acids"); + } + tracing::info!("Deduplicated to {} unique peptides", deduped.len()); + deduped + } else if let Some(list_path) = &config.peptide_list { + tracing::info!("Reading peptide list: {}", list_path.display()); + let file = std::fs::File::open(list_path)?; + let reader = std::io::BufReader::new(file); + let mut slices: Vec = Vec::new(); + for (i, line) in reader.lines().enumerate() { + let line = line?; + let seq = line.trim().to_string(); + if seq.is_empty() || seq.starts_with('#') { + continue; + } + slices.push(DigestSlice::from_string(seq, false, i as u32)); + } + tracing::info!("Read {} peptides from list", slices.len()); + + let estimated = slices.len(); + let deduped = PeptideDedup::dedup(slices, estimated); + let (deduped, skipped) = filter_nonstandard_aa(deduped); + if skipped > 0 { + tracing::warn!("Skipped {skipped} peptides containing non-standard amino acids"); + } + tracing::info!("Deduplicated to {} unique peptides", deduped.len()); + deduped + } else { + return Err("No input provided (--fasta or --peptide-list)".into()); + }; + + if base_peptides.is_empty() { + return Err("No peptides after digestion/dedup — nothing to do".into()); + } + + // ── Phase 2: Set up prediction ────────────────────────────────────────── + + let fragment_model = FragmentModel::from_name(&config.prediction.fragment_model)?; + let rt_model = RtModel::from_name(&config.prediction.rt_model)?; + let koina = KoinaClient::new( + config.prediction.koina_url.clone(), + fragment_model, + rt_model, + ); + + let fixed_mods: Vec = config + .modifications + .fixed + .iter() + .map(|s| Modification::parse(s)) + .collect::, _>>()?; + + let var_mods: Vec = config + .modifications + .variable + .iter() + .map(|s| Modification::parse(s)) + .collect::, _>>()?; + + let decoy_mode = DecoyMode::from_str(&config.decoys.strategy)?; + + let filters = EntryFilters { + max_ions: config.filters.max_ions, + min_mz: config.filters.min_mz, + max_mz: config.filters.max_mz, + min_ion_mz: config.filters.min_ion_mz, + max_ion_mz: config.filters.max_ion_mz, + min_ions: config.filters.min_ions, + }; + + let nce = config.prediction.nce; + let batch_size = config.prediction.batch_size; + let request_delay = if config.prediction.request_delay_ms > 0 { + Some(std::time::Duration::from_millis(config.prediction.request_delay_ms)) + } else { + None + }; + let charges: Vec = (config.charges.min..=config.charges.max).collect(); + let max_var_mods = config.modifications.max_variable; + + // ── Phase 3: Streaming expansion + prediction ─────────────────────────── + + let out_file = std::fs::File::create(&config.output)?; + let mut writer = SpeclibWriter::new_msgpack_zstd(out_file)?; + + // Estimate total items for progress bar: peptides * mod_variants * charges * (1 + decoy) + let decoy_mult: u64 = if decoy_mode != DecoyMode::None { 2 } else { 1 }; + let estimated_total = + base_peptides.len() as u64 * charges.len() as u64 * decoy_mult; + let progress = ProgressBar::new(estimated_total); + progress.set_style( + ProgressStyle::with_template( + "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({per_sec}, ETA {eta})", + ) + .unwrap() + .progress_chars("#>-"), + ); + + let mut batch: Vec = Vec::with_capacity(batch_size); + let mut entry_id: u32 = 0; + let mut decoy_group: u32 = 0; + + for digest_slice in &base_peptides { + let base_seq = digest_slice.as_str(); + + // Apply fixed modifications. + let fixed_seq = if fixed_mods.is_empty() { + base_seq.to_string() + } else { + apply_fixed_mods(base_seq, &fixed_mods) + }; + + // Expand variable modifications. + let mod_variants = if var_mods.is_empty() { + vec![fixed_seq] + } else { + expand_variable_mods(&fixed_seq, &var_mods, max_var_mods) + }; + + for variant in &mod_variants { + for &charge in &charges { + // Target + batch.push(BatchItem { + sequence: variant.clone(), + charge, + decoy: false, + decoy_group, + }); + + // Decoy + if decoy_mode != DecoyMode::None { + let decoy_seq = generate_decoy(variant, decoy_mode); + batch.push(BatchItem { + sequence: decoy_seq, + charge, + decoy: true, + decoy_group, + }); + } + + if batch.len() >= batch_size { + flush_batch( + &koina, + &mut writer, + &mut batch, + &filters, + nce, + &mut entry_id, + &progress, + ) + .await?; + if let Some(delay) = request_delay { + tokio::time::sleep(delay).await; + } + } + } + } + + decoy_group += 1; + } + + // Flush remaining items. + flush_batch( + &koina, + &mut writer, + &mut batch, + &filters, + nce, + &mut entry_id, + &progress, + ) + .await?; + + progress.finish_with_message("done"); + writer.finish()?; + + tracing::info!( + "Wrote {} entries to {}", + entry_id, + config.output.display(), + ); + + Ok(()) +} + +const NONSTANDARD_AA: &[char] = &['U', 'B', 'J', 'Z', 'X']; + +/// Filter out peptides containing non-standard amino acids (U, B, J, Z, X). +/// Returns (kept, skipped_count). +fn filter_nonstandard_aa(peptides: Vec) -> (Vec, usize) { + let before = peptides.len(); + let kept: Vec = peptides + .into_iter() + .filter(|p| !p.as_str().chars().any(|c| NONSTANDARD_AA.contains(&c))) + .collect(); + let skipped = before - kept.len(); + (kept, skipped) +} diff --git a/rust/speclib_build_cli/tests/integration.rs b/rust/speclib_build_cli/tests/integration.rs new file mode 100644 index 0000000..ba285c4 --- /dev/null +++ b/rust/speclib_build_cli/tests/integration.rs @@ -0,0 +1,70 @@ +use std::path::PathBuf; + +fn test_fasta_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("test_data") + .join("tiny.fasta") +} + +#[test] +fn test_pipeline_digestion_and_dedup() { + let fasta_path = test_fasta_path(); + assert!(fasta_path.exists()); + + use timsseek::protein::fasta::ProteinSequenceCollection; + use timsseek::digest::digestion::*; + + let proteins = ProteinSequenceCollection::from_fasta_file(&fasta_path).unwrap(); + assert_eq!(proteins.sequences.len(), 2); + + let params = DigestionParameters { + min_length: 6, + max_length: 25, + pattern: DigestionPattern::trypsin(), + digestion_end: DigestionEnd::CTerm, + max_missed_cleavages: 1, + }; + + let all_digests: Vec<_> = proteins.sequences.iter() + .flat_map(|p| params.digest(p.sequence.clone())) + .collect(); + assert!(all_digests.len() > 0); + + use speclib_build_cli::dedup::PeptideDedup; + let deduped = PeptideDedup::dedup(all_digests.clone(), 100); + assert!(deduped.len() <= all_digests.len()); + assert!(deduped.len() > 0); +} + +#[test] +fn test_mod_application_chain() { + use speclib_build_cli::mods::*; + + let fixed = vec![Modification::parse("C[U:4]").unwrap()]; + let var = vec![Modification::parse("M[U:35]").unwrap()]; + + let seq = "PEPTMCIDECK"; + let modified = apply_fixed_mods(seq, &fixed); + assert_eq!(modified, "PEPTMC[U:4]IDEC[U:4]K"); + + let expanded = expand_variable_mods(&modified, &var, 1); + assert!(expanded.len() == 2); // unmodified + M oxidized + assert!(expanded.contains(&modified)); +} + +#[test] +fn test_decoy_roundtrip() { + use speclib_build_cli::decoys::*; + + let original = "PEPTIDEK"; + let reversed = generate_decoy(original, DecoyMode::Reverse); + assert_ne!(reversed, original); + assert_eq!(reversed.len(), original.len()); + assert_eq!(reversed.chars().next(), original.chars().next()); + assert_eq!(reversed.chars().last(), original.chars().last()); + + let edge_mutated = generate_decoy(original, DecoyMode::EdgeMutate); + assert_ne!(edge_mutated, original); + assert_eq!(edge_mutated.len(), original.len()); +} diff --git a/rust/speclib_build_cli/tests/test_data/tiny.fasta b/rust/speclib_build_cli/tests/test_data/tiny.fasta new file mode 100644 index 0000000..1e2c347 --- /dev/null +++ b/rust/speclib_build_cli/tests/test_data/tiny.fasta @@ -0,0 +1,4 @@ +>sp|P00001|TEST_HUMAN Test protein +MYPEPTIDEKFOOBARKANOTHERPEPTIDEK +>sp|P00002|TEST2_HUMAN Test protein 2 +PEPTIDEPINKSHAREDPEPTIDEKFINAL diff --git a/rust/timsquery/src/models/tolerance.rs b/rust/timsquery/src/models/tolerance.rs index 4bca5ef..eb33510 100644 --- a/rust/timsquery/src/models/tolerance.rs +++ b/rust/timsquery/src/models/tolerance.rs @@ -82,7 +82,7 @@ impl Default for Tolerance { fn default() -> Self { Tolerance { ms: MzTolerance::Ppm((20.0, 20.0)), - rt: RtTolerance::Minutes((5.0, 5.0)), + rt: RtTolerance::Unrestricted, mobility: MobilityTolerance::Pct((3.0, 3.0)), quad: QuadTolerance::Absolute((0.1, 0.1)), } diff --git a/rust/timsquery/src/serde/library_file.rs b/rust/timsquery/src/serde/library_file.rs index 1da6597..dee9208 100644 --- a/rust/timsquery/src/serde/library_file.rs +++ b/rust/timsquery/src/serde/library_file.rs @@ -9,6 +9,11 @@ use super::elution_group_inputs::{ ElutionGroupInput, ElutionGroupInputError, }; +pub use super::skyline_io::SkylinePrecursorExtras; +use super::skyline_io::{ + read_library_file as read_skyline_csv, + sniff_skyline_library_file, +}; pub use super::spectronaut_io::SpectronautPrecursorExtras; use super::spectronaut_io::{ read_library_file as read_spectronaut_tsv, @@ -46,6 +51,7 @@ impl From for LibraryReadingError { pub enum FileReadingExtras { Diann(Vec), Spectronaut(Vec), + Skyline(Vec), } #[derive(Debug)] @@ -194,6 +200,31 @@ impl ElutionGroupCollection { Err(LibraryReadingError::UnableToParseElutionGroups) } + fn try_read_skyline(path: &Path) -> Result { + match sniff_skyline_library_file(path) { + Ok(()) => { + info!("Detected Skyline transition list CSV"); + let egs = match read_skyline_csv(path) { + Ok(egs) => egs, + Err(e) => { + warn!("Failed to read Skyline transition list: {:?}", e); + return Err(LibraryReadingError::UnableToParseElutionGroups); + } + }; + let (egs, extras): (Vec<_>, Vec<_>) = egs.into_iter().unzip(); + info!("Successfully read Skyline transition list"); + Ok(ElutionGroupCollection::MzpafLabels( + egs, + Some(FileReadingExtras::Skyline(extras)), + )) + } + Err(e) => { + debug!("File is not Skyline format: {:?}", e); + Err(LibraryReadingError::UnableToParseElutionGroups) + } + } + } + fn try_read_spectronaut(path: &Path) -> Result { match sniff_spectronaut_library_file(path) { Ok(()) => { @@ -235,6 +266,12 @@ pub fn read_library_file>( return Ok(egs); } + // Try Skyline transition list (CSV) + let skyline_attempt = ElutionGroupCollection::try_read_skyline(path.as_ref()); + if let Ok(egs) = skyline_attempt { + return Ok(egs); + } + // Fall back to JSON ElutionGroupCollection::try_read_json(path.as_ref()) } diff --git a/rust/timsquery/src/serde/mod.rs b/rust/timsquery/src/serde/mod.rs index bcf272f..7ff1a7e 100644 --- a/rust/timsquery/src/serde/mod.rs +++ b/rust/timsquery/src/serde/mod.rs @@ -3,6 +3,7 @@ mod diann_io; mod elution_group_inputs; pub mod index_serde; mod library_file; +mod skyline_io; mod spectronaut_io; pub use chromatogram_output::*; @@ -12,6 +13,7 @@ pub use library_file::{ ElutionGroupCollection, FileReadingExtras, LibraryReadingError, + SkylinePrecursorExtras, SpectronautPrecursorExtras, read_library_file, }; diff --git a/rust/timsquery/src/serde/skyline_io.rs b/rust/timsquery/src/serde/skyline_io.rs new file mode 100644 index 0000000..51bab0c --- /dev/null +++ b/rust/timsquery/src/serde/skyline_io.rs @@ -0,0 +1,490 @@ +use crate::TimsElutionGroup; +use crate::ion::{ + IonAnnot, + IonParsingError, +}; +use serde::{ + Deserialize, + Deserializer, +}; +use std::path::Path; +use tinyvec::tiny_vec; +use tracing::{ + debug, + error, + info, + warn, +}; + +#[derive(Debug)] +pub enum SkylineReadingError { + IoError, + CsvError, + SkylinePrecursorParsingError, +} + +#[derive(Debug)] +#[allow(dead_code)] // fields surfaced only through Debug for tracing +pub enum SkylineSniffError { + IoError(String), + InvalidFormat(String), + MissingColumns(Vec), +} + +impl std::fmt::Display for SkylineReadingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SkylineReadingError::IoError => write!(f, "IO error"), + SkylineReadingError::CsvError => write!(f, "CSV parsing error"), + SkylineReadingError::SkylinePrecursorParsingError => { + write!(f, "Skyline precursor parsing error") + } + } + } +} + +impl std::error::Error for SkylineReadingError {} + +#[derive(Debug)] +pub enum SkylinePrecursorParsingError { + IonParsingError, + IonOverCapacity, + Other, +} + +impl From for SkylinePrecursorParsingError { + fn from(err: IonParsingError) -> Self { + error!("Ion parsing error: {:?}", err); + SkylinePrecursorParsingError::IonParsingError + } +} + +impl From for SkylineReadingError { + fn from(_err: SkylinePrecursorParsingError) -> Self { + SkylineReadingError::SkylinePrecursorParsingError + } +} + +impl From for SkylineReadingError { + fn from(err: csv::Error) -> Self { + error!("CSV reading error: {:?}", err); + SkylineReadingError::CsvError + } +} + +impl From for SkylineReadingError { + fn from(err: std::io::Error) -> Self { + error!("IO error: {:?}", err); + SkylineReadingError::IoError + } +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct SkylinePrecursorExtras { + pub modified_peptide: String, + pub stripped_peptide: String, + pub protein_id: String, + pub is_decoy: bool, + pub relative_intensities: Vec<(IonAnnot, f32)>, +} + +/// Deserializer that treats Skyline's `#N/A` sentinel as `None`. +fn na_to_none<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let trimmed = s.trim(); + if trimmed.is_empty() || trimmed == "#N/A" { + return Ok(None); + } + trimmed.parse::().map(Some).map_err(serde::de::Error::custom) +} + +/// Represents a single row from a Skyline Peptide Transition List CSV. +#[derive(Debug, Clone, Deserialize)] +struct SkylineLibraryRow { + #[serde(rename = "Protein Name")] + protein_name: String, + #[serde(rename = "Peptide Modified Sequence")] + peptide_modified_sequence: String, + #[serde(rename = "Precursor Mz")] + precursor_mz: f64, + #[serde(rename = "Precursor Charge")] + precursor_charge: i32, + #[serde(rename = "Product Mz")] + product_mz: f64, + #[serde(rename = "Product Charge")] + product_charge: i32, + #[serde(rename = "Fragment Ion Type")] + fragment_ion_type: String, + #[serde(rename = "Fragment Ion Ordinal")] + fragment_ion_ordinal: i32, + #[serde(rename = "Library Intensity", default, deserialize_with = "na_to_none")] + library_intensity: Option, + #[serde(rename = "Transition Is Decoy")] + transition_is_decoy: String, +} + +impl SkylineLibraryRow { + fn is_same_precursor(&self, other: &SkylineLibraryRow) -> bool { + self.peptide_modified_sequence == other.peptide_modified_sequence + && self.precursor_charge == other.precursor_charge + && self.precursor_mz == other.precursor_mz + && self.protein_name == other.protein_name + && self.transition_is_decoy == other.transition_is_decoy + } + + fn is_precursor_row(&self) -> bool { + self.fragment_ion_type.eq_ignore_ascii_case("precursor") + } + + fn is_decoy(&self) -> Result { + match self.transition_is_decoy.trim() { + "True" | "true" | "TRUE" | "1" => Ok(true), + "False" | "false" | "FALSE" | "0" => Ok(false), + other => { + error!("Unexpected `Transition Is Decoy` value: {}", other); + Err(SkylinePrecursorParsingError::Other) + } + } + } +} + +/// Remove bracketed modification annotations, e.g. `C[+57.02]AM` -> `CAM`. +fn strip_modifications(modified_seq: &str) -> String { + let mut out = String::with_capacity(modified_seq.len()); + let mut depth: i32 = 0; + for ch in modified_seq.chars() { + match ch { + '[' | '(' | '{' => depth += 1, + ']' | ')' | '}' => { + if depth > 0 { + depth -= 1; + } + } + _ if depth == 0 => out.push(ch), + _ => {} + } + } + out +} + +/// Check if a file is a Skyline Peptide Transition List CSV. +pub fn sniff_skyline_library_file>(file: T) -> Result<(), SkylineSniffError> { + let file_handle = std::fs::File::open(file.as_ref()).map_err(|e| { + SkylineSniffError::IoError(format!("Failed to open {}: {}", file.as_ref().display(), e)) + })?; + + let mut rdr = csv::ReaderBuilder::new().delimiter(b',').from_reader(file_handle); + + let headers = rdr.headers().map_err(|e| { + SkylineSniffError::InvalidFormat(format!("Failed to parse CSV headers: {}", e)) + })?; + + let columns: Vec = headers.iter().map(|s| s.to_string()).collect(); + + // Required columns for a Skyline transition list. The combination of + // "Peptide Modified Sequence", "Fragment Ion Type/Ordinal" and + // "Transition Is Decoy" is distinctive to Skyline exports. + let required_columns = [ + "Protein Name", + "Peptide Modified Sequence", + "Precursor Mz", + "Precursor Charge", + "Product Mz", + "Product Charge", + "Fragment Ion Type", + "Fragment Ion Ordinal", + "Transition Is Decoy", + ]; + + let missing: Vec = required_columns + .iter() + .filter(|col| !columns.contains(&col.to_string())) + .map(|s| s.to_string()) + .collect(); + + if missing.is_empty() { + Ok(()) + } else { + Err(SkylineSniffError::MissingColumns(missing)) + } +} + +struct ParsingBuffers { + fragment_labels: Vec, +} + +pub fn read_library_file>( + file: T, +) -> Result, SkylinePrecursorExtras)>, SkylineReadingError> { + let file_handle = std::fs::File::open(file.as_ref())?; + + let mut rdr = csv::ReaderBuilder::new().delimiter(b',').from_reader(file_handle); + + info!("Reading Skyline transition list from {}", file.as_ref().display()); + warn!( + "Skyline transition lists do not carry retention time or ion mobility; \ + falling back to 0.0. Use an RT-unrestricted search or calibrate separately." + ); + + let mut elution_groups = Vec::new(); + let mut current_group: Vec = Vec::with_capacity(20); + let mut buffers = ParsingBuffers { + fragment_labels: Vec::with_capacity(20), + }; + let mut group_id = 0u64; + + for result in rdr.deserialize() { + let row: SkylineLibraryRow = result?; + + if let Some(last_row) = current_group.first() + && !row.is_same_precursor(last_row) + { + if let Some(eg) = parse_precursor_group(group_id, ¤t_group, &mut buffers)? { + elution_groups.push(eg); + group_id += 1; + } + current_group.clear(); + } + + current_group.push(row); + } + + if !current_group.is_empty() + && let Some(eg) = parse_precursor_group(group_id, ¤t_group, &mut buffers)? + { + elution_groups.push(eg); + } + + info!("Parsed {} elution groups", elution_groups.len()); + Ok(elution_groups) +} + +fn parse_precursor_group( + id: u64, + rows: &[SkylineLibraryRow], + buffers: &mut ParsingBuffers, +) -> Result< + Option<(TimsElutionGroup, SkylinePrecursorExtras)>, + SkylinePrecursorParsingError, +> { + if rows.is_empty() { + error!("Empty precursor group encountered on {id}"); + return Err(SkylinePrecursorParsingError::Other); + } + + // Fragments are every non-precursor row. Precursor (isotope) rows are + // ignored — downstream computes the isotope envelope from the peptide + // sequence, matching the DIA-NN path. + let fragment_rows: Vec<&SkylineLibraryRow> = + rows.iter().filter(|r| !r.is_precursor_row()).collect(); + + if fragment_rows.is_empty() { + warn!( + "Skyline precursor group {} has no product-ion rows; skipping", + id + ); + return Ok(None); + } + + let first_row = &rows[0]; + let precursor_mz = first_row.precursor_mz; + let precursor_charge: u8 = + first_row + .precursor_charge + .try_into() + .map_err(|e: std::num::TryFromIntError| { + error!("Failed to convert PrecursorCharge to u8: {:?}", e); + SkylinePrecursorParsingError::IonOverCapacity + })?; + let is_decoy = first_row.is_decoy()?; + + let mut fragment_mzs = Vec::with_capacity(fragment_rows.len()); + buffers.fragment_labels.clear(); + let mut relative_intensities = Vec::with_capacity(fragment_rows.len()); + let mut num_unknown_losses = 0u8; + + for (i, row) in fragment_rows.iter().enumerate() { + let fragment_mz = row.product_mz; + let frag_charge: u8 = + row.product_charge + .try_into() + .map_err(|e: std::num::TryFromIntError| { + error!("Failed to convert Product Charge to u8: {:?}", e); + SkylinePrecursorParsingError::IonOverCapacity + })?; + // Skyline transition lists often lack library intensities (#N/A). + // Default to 1.0; downstream normalization handles relative scaling. + let rel_intensity = row.library_intensity.unwrap_or(1.0); + + let frag_type = row.fragment_ion_type.trim(); + // Known backbone series: a/b/c/x/y/z. Anything else -> unknown ion. + let frag_char_opt = frag_type + .chars() + .next() + .filter(|c| matches!(c, 'a' | 'b' | 'c' | 'x' | 'y' | 'z')); + + let ion_annot = match frag_char_opt { + Some(frag_char) => { + let frag_num: u8 = row.fragment_ion_ordinal.try_into().map_err(|_| { + error!( + "Invalid fragment ion ordinal (expected < 255): {}", + row.fragment_ion_ordinal + ); + SkylinePrecursorParsingError::IonOverCapacity + })?; + IonAnnot::try_new(frag_char, Some(frag_num), frag_charge as i8, 0)? + } + None => { + warn!( + "Unsupported Skyline fragment ion type '{}' at row {}; \ + falling back to unknown ion", + frag_type, i + ); + num_unknown_losses = num_unknown_losses.saturating_add(1); + IonAnnot::try_new('?', Some(num_unknown_losses), frag_charge as i8, 0)? + } + }; + + buffers.fragment_labels.push(ion_annot); + fragment_mzs.push(fragment_mz); + relative_intensities.push((ion_annot, rel_intensity)); + } + + let modified_peptide = first_row.peptide_modified_sequence.clone(); + let stripped_peptide = strip_modifications(&modified_peptide); + + let precursor_extras = SkylinePrecursorExtras { + modified_peptide, + stripped_peptide, + protein_id: first_row.protein_name.clone(), + is_decoy, + relative_intensities, + }; + + let eg = TimsElutionGroup::builder() + .id(id) + .mobility_ook0(0.0) + .rt_seconds(0.0) + .fragment_labels(buffers.fragment_labels.as_slice().into()) + .fragment_mzs(fragment_mzs) + .precursor_labels(tiny_vec![0]) + .precursor(precursor_mz, precursor_charge) + .try_build() + .unwrap(); + + debug!( + "Parsed Skyline precursor group {}: {} fragments, decoy={}", + id, + buffers.fragment_labels.len(), + is_decoy + ); + + Ok(Some((eg, precursor_extras))) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn fixture_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("skyline_io_files") + .join("sample_transition_list.csv") + } + + #[test] + fn test_strip_modifications() { + assert_eq!(strip_modifications("PEPTIDE"), "PEPTIDE"); + assert_eq!(strip_modifications("C[+57.021]AM"), "CAM"); + assert_eq!(strip_modifications("P[UniMod:35]IDE"), "PIDE"); + assert_eq!(strip_modifications("[+42]AB"), "AB"); + } + + #[test] + fn test_sniff_skyline_library_file() { + let file_path = fixture_path(); + let result = sniff_skyline_library_file(&file_path); + assert!( + result.is_ok(), + "File should be detected as Skyline library: {:?}", + result.err() + ); + + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let non_skyline = PathBuf::from(manifest_dir).join("Cargo.toml"); + assert!( + sniff_skyline_library_file(non_skyline).is_err(), + "Cargo.toml should not be detected as Skyline library" + ); + } + + #[test] + fn test_sniff_diann_not_skyline() { + let diann_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("diann_io_files") + .join("sample_lib.txt"); + let result = sniff_skyline_library_file(diann_file); + assert!( + result.is_err(), + "DIA-NN library should not be detected as Skyline library" + ); + } + + #[test] + fn test_read_library_file() { + let elution_groups = + read_library_file(fixture_path()).expect("Failed to read Skyline library"); + + // Fixture has 14 distinct PRTC peptides + assert_eq!(elution_groups.len(), 14, "Expected 14 elution groups"); + + for (eg, extras) in &elution_groups { + assert_eq!(extras.protein_id, "PRTC"); + assert!(!extras.is_decoy); + assert_eq!(extras.modified_peptide, extras.stripped_peptide); + assert!(eg.fragment_count() > 0, "Each precursor should have fragments"); + assert_eq!( + extras.relative_intensities.len(), + eg.fragment_count(), + "Relative intensities should match fragment count" + ); + } + } + + #[test] + fn test_precursor_isotope_rows_are_skipped() { + let elution_groups = read_library_file(fixture_path()).expect("Failed to read library"); + + // SSAAPPPPPR has 3 precursor rows + 4 y fragments in the fixture + let ssaa = elution_groups + .iter() + .find(|(_, extras)| extras.stripped_peptide == "SSAAPPPPPR") + .expect("SSAAPPPPPR should be present"); + assert_eq!( + ssaa.0.fragment_count(), + 4, + "SSAAPPPPPR should have 4 product-ion fragments (precursor rows skipped)" + ); + } + + #[test] + fn test_library_intensity_na_handling() { + // All intensities in the fixture are #N/A -> default to 1.0 + let elution_groups = read_library_file(fixture_path()).expect("Failed to read library"); + for (_, extras) in &elution_groups { + for (_lab, intensity) in &extras.relative_intensities { + assert!( + (*intensity - 1.0).abs() < f32::EPSILON, + "Expected default intensity 1.0, got {}", + intensity + ); + } + } + } +} diff --git a/rust/timsquery/tests/skyline_io_files/sample_transition_list.csv b/rust/timsquery/tests/skyline_io_files/sample_transition_list.csv new file mode 100755 index 0000000..e3e4be9 --- /dev/null +++ b/rust/timsquery/tests/skyline_io_files/sample_transition_list.csv @@ -0,0 +1,100 @@ +Protein Name,Peptide Modified Sequence,Precursor Mz,Precursor Charge,Collision Energy,Product Mz,Product Charge,Fragment Ion,Fragment Ion Type,Fragment Ion Ordinal,Cleavage Aa,Loss Neutral Mass,Losses,Library Rank,Library Intensity,Isotope Dist Index,Isotope Dist Rank,Isotope Dist Proportion,Full Scan Filter Width,Transition Is Decoy,Product Decoy Mz Shift +PRTC,SSAAPPPPPR,493.7683,2,17.6,493.7683,2,precursor,precursor,10,R,0,,#N/A,#N/A,0,1,0.6035,0.032918,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,494.269797,2,precursor [M+1],precursor,10,R,0,,#N/A,#N/A,1,2,0.2713,0.032951,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,494.771095,2,precursor [M+2],precursor,10,R,0,,#N/A,#N/A,2,3,0.0758,0.032985,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,670.39104,1,y6,y,6,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.044693,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,573.338276,1,y5,y,5,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.038223,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,476.285512,1,y4,y,4,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.031752,False,#N/A +PRTC,SSAAPPPPPR,493.7683,2,17.6,379.232748,1,y3,y,3,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.025282,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,613.316765,2,precursor,precursor,12,K,0,,#N/A,#N/A,0,1,0.5495,0.040888,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,613.818187,2,precursor [M+1],precursor,12,K,0,,#N/A,#N/A,1,2,0.2957,0.040921,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,614.31944,2,precursor [M+2],precursor,12,K,0,,#N/A,#N/A,2,3,0.1007,0.040955,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,1055.520727,1,y10,y,10,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.070368,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,968.488698,1,y9,y,9,N,0,,#N/A,#N/A,0,#N/A,#N/A,0.064566,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,854.445771,1,y8,y,8,E,0,,#N/A,#N/A,0,#N/A,#N/A,0.056963,False,#N/A +PRTC,GISNEGQNASIK,613.316765,2,21.2,725.403178,1,y7,y,7,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.04836,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,496.286748,2,precursor,precursor,9,K,0,,#N/A,#N/A,0,1,0.6028,0.033086,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,496.788216,2,precursor [M+1],precursor,9,K,0,,#N/A,#N/A,1,2,0.2744,0.033119,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,497.289497,2,precursor [M+2],precursor,9,K,0,,#N/A,#N/A,2,3,0.0784,0.033153,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,755.438895,1,y7,y,7,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.050363,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,642.354831,1,y6,y,6,T,0,,#N/A,#N/A,0,#N/A,#N/A,0.042824,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,541.307152,1,y5,y,5,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.036087,False,#N/A +PRTC,HVLTSIGEK,496.286748,2,17.7,341.19106,1,y3,y,3,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.022746,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,451.283477,2,precursor,precursor,8,K,0,,#N/A,#N/A,0,1,0.6181,0.030086,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,451.784987,2,precursor [M+1],precursor,8,K,0,,#N/A,#N/A,1,2,0.2692,0.030119,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,452.286313,2,precursor [M+2],precursor,8,K,0,,#N/A,#N/A,2,3,0.0713,0.030152,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,673.448671,1,y6,y,6,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.044897,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,576.395907,1,y5,y,5,V,0,,#N/A,#N/A,0,#N/A,#N/A,0.038426,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,477.327494,1,y4,y,4,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.031822,False,#N/A +PRTC,DIPVPKPK,451.283477,2,16.3,252.179767,1,y2,y,2,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.016812,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,422.73636,2,precursor,precursor,8,K,0,,#N/A,#N/A,0,1,0.6462,0.028182,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,423.237871,2,precursor [M+1],precursor,8,K,0,,#N/A,#N/A,1,2,0.2505,0.028216,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,423.73915,2,precursor [M+2],precursor,8,K,0,,#N/A,#N/A,2,3,0.0632,0.028249,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,674.359916,1,y6,y,6,D,0,,#N/A,#N/A,0,#N/A,#N/A,0.044957,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,559.332973,1,y5,y,5,Y,0,,#N/A,#N/A,0,#N/A,#N/A,0.037289,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,396.269644,1,y4,y,4,A,0,,#N/A,#N/A,0,#N/A,#N/A,0.026418,False,#N/A +PRTC,IGDYAGIK,422.73636,2,15.5,325.23253,1,y3,y,3,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.021682,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,695.832445,2,precursor,precursor,13,K,0,,#N/A,#N/A,0,1,0.4966,0.046389,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,696.33393,2,precursor [M+1],precursor,13,K,0,,#N/A,#N/A,1,2,0.3146,0.046422,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,696.835238,2,precursor [M+2],precursor,13,K,0,,#N/A,#N/A,2,3,0.1228,0.046456,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,1218.572822,1,y11,y,11,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.081238,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,1002.4982,1,y9,y,9,F,0,,#N/A,#N/A,0,#N/A,#N/A,0.066833,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,855.429786,1,y8,y,8,D,0,,#N/A,#N/A,0,#N/A,#N/A,0.057029,False,#N/A +PRTC,TASEFDSAIAQDK,695.832445,2,23.7,740.402843,1,y7,y,7,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.04936,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,586.800329,2,precursor,precursor,12,R,0,,#N/A,#N/A,0,1,0.5513,0.03912,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,587.301821,2,precursor [M+1],precursor,12,R,0,,#N/A,#N/A,1,2,0.2949,0.039153,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,587.803127,2,precursor [M+2],precursor,12,R,0,,#N/A,#N/A,2,3,0.0968,0.039187,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,943.487125,1,y9,y,9,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.062899,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,815.428548,1,y7,y,7,F,0,,#N/A,#N/A,0,#N/A,#N/A,0.054362,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,668.360134,1,y6,y,6,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.044557,False,#N/A +PRTC,SAAGAFGPELSR,586.800329,2,20.4,611.33867,1,y5,y,5,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.040756,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,773.895577,2,precursor,precursor,14,K,0,,#N/A,#N/A,0,1,0.4514,0.051593,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,774.397062,2,precursor [M+1],precursor,14,K,0,,#N/A,#N/A,1,2,0.329,0.051626,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,774.898401,2,precursor [M+2],precursor,14,K,0,,#N/A,#N/A,2,3,0.1419,0.05166,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,1304.65722,1,y12,y,12,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.086977,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,1119.577179,1,y10,y,10,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.074638,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,1032.545151,1,y9,y,9,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.068836,False,#N/A +PRTC,ELGQSGVDTYLQTK,773.895577,2,26,876.455273,1,y7,y,7,D,0,,#N/A,#N/A,0,#N/A,#N/A,0.05843,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,558.325982,2,precursor,precursor,11,R,0,,#N/A,#N/A,0,1,0.5569,0.037222,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,558.827496,2,precursor [M+1],precursor,11,R,0,,#N/A,#N/A,1,2,0.2955,0.037255,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,559.328838,2,precursor [M+2],precursor,11,R,0,,#N/A,#N/A,2,3,0.0932,0.037289,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,832.455097,1,y8,y,8,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.055497,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,719.371033,1,y7,y,7,V,0,,#N/A,#N/A,0,#N/A,#N/A,0.047958,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,620.302619,1,y6,y,6,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.041354,False,#N/A +PRTC,GLILVGGYGTR,558.325982,2,19.5,563.281155,1,y5,y,5,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.037552,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,801.411503,2,precursor,precursor,17,R,0,,#N/A,#N/A,0,1,0.4403,0.053427,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,801.912993,2,precursor [M+1],precursor,17,R,0,,#N/A,#N/A,1,2,0.3308,0.053461,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,802.414334,2,precursor [M+2],precursor,17,R,0,,#N/A,#N/A,2,3,0.145,0.053494,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,1171.557724,1,y13,y,13,V,0,,#N/A,#N/A,0,#N/A,#N/A,0.078104,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,1072.48931,1,y12,y,12,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.071499,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,1015.467847,1,y11,y,11,S,0,,#N/A,#N/A,0,#N/A,#N/A,0.067698,False,#N/A +PRTC,GILFVGSGVSGGEEGAR,801.411503,2,26.8,928.435818,1,y10,y,10,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.061896,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,745.392473,2,precursor,precursor,13,K,0,,#N/A,#N/A,0,1,0.4481,0.049693,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,745.893977,2,precursor [M+1],precursor,13,K,0,,#N/A,#N/A,1,2,0.3335,0.049726,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,746.395351,2,precursor [M+2],precursor,13,K,0,,#N/A,#N/A,2,3,0.1424,0.04976,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,1255.677227,1,y11,y,11,A,0,,#N/A,#N/A,0,#N/A,#N/A,0.083712,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,1184.640114,1,y10,y,10,N,0,,#N/A,#N/A,0,#N/A,#N/A,0.078976,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,1070.597186,1,y9,y,9,Q,0,,#N/A,#N/A,0,#N/A,#N/A,0.071373,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,942.538609,1,y8,y,8,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.062836,False,#N/A +PRTC,SFANQPLEVVYSK,745.392473,2,25.1,845.485845,1,y7,y,7,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.056366,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,498.801809,2,precursor,precursor,8,R,0,,#N/A,#N/A,0,1,0.5992,0.033253,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,499.303364,2,precursor [M+1],precursor,8,R,0,,#N/A,#N/A,1,2,0.2726,0.033287,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,499.804689,2,precursor [M+2],precursor,8,R,0,,#N/A,#N/A,2,3,0.0779,0.03332,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,782.464599,1,y6,y,6,I,0,,#N/A,#N/A,0,#N/A,#N/A,0.052164,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,669.380535,1,y5,y,5,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.044625,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,556.296471,1,y4,y,4,E,0,,#N/A,#N/A,0,#N/A,#N/A,0.037086,False,#N/A +PRTC,LTILEELR,498.801809,2,17.7,427.253878,1,y3,y,3,E,0,,#N/A,#N/A,0,#N/A,#N/A,0.028484,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,573.302507,2,precursor,precursor,10,R,0,,#N/A,#N/A,0,1,0.5403,0.03822,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,573.804029,2,precursor [M+1],precursor,10,R,0,,#N/A,#N/A,1,2,0.3035,0.038254,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,574.305391,2,precursor [M+2],precursor,10,R,0,,#N/A,#N/A,2,3,0.0996,0.038287,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,827.464933,1,y7,y,7,I,0,,#N/A,#N/A,0,#N/A,#N/A,0.055164,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,714.380869,1,y6,y,6,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.047625,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,601.296805,1,y5,y,5,D,0,,#N/A,#N/A,0,#N/A,#N/A,0.040086,False,#N/A +PRTC,NGFILDGFPR,573.302507,2,20,486.269862,1,y4,y,4,G,0,,#N/A,#N/A,0,#N/A,#N/A,0.032418,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,787.42123,2,precursor,precursor,14,K,0,,#N/A,#N/A,0,1,0.4238,0.052495,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,787.92275,2,precursor [M+1],precursor,14,K,0,,#N/A,#N/A,1,2,0.3386,0.052528,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,788.424145,2,precursor [M+2],precursor,14,K,0,,#N/A,#N/A,2,3,0.1535,0.052562,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,1157.644471,1,y10,y,10,A,0,,#N/A,#N/A,0,#N/A,#N/A,0.077176,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,1086.607357,1,y9,y,9,P,0,,#N/A,#N/A,0,#N/A,#N/A,0.07244,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,918.517479,1,y7,y,7,L,0,,#N/A,#N/A,0,#N/A,#N/A,0.061234,False,#N/A +PRTC,LSSEAPALFQFDLK,787.42123,2,26.4,805.433415,1,y6,y,6,F,0,,#N/A,#N/A,0,#N/A,#N/A,0.053696,False,#N/A diff --git a/rust/timsquery_viewer/src/file_loader.rs b/rust/timsquery_viewer/src/file_loader.rs index 166f172..c741be6 100644 --- a/rust/timsquery_viewer/src/file_loader.rs +++ b/rust/timsquery_viewer/src/file_loader.rs @@ -258,6 +258,14 @@ impl ElutionGroupData { is_decoy: se.is_decoy, }) } + FileReadingExtras::Skyline(skyline_extras) => { + let sky = skyline_extras.get(idx)?; + Some(LibraryExtrasView { + modified_peptide: sky.modified_peptide.clone(), + protein_id: sky.protein_id.clone(), + is_decoy: sky.is_decoy, + }) + } } } @@ -355,6 +363,19 @@ impl ElutionGroupData { &mut eg, ) } + Some(FileReadingExtras::Skyline(skyline_extras)) => { + let sky = skyline_extras + .get(index) + .ok_or(ViewerError::General(format!( + "Skyline extras index {} out of bounds", + index + )))?; + Self::build_expected_intensities( + &sky.stripped_peptide, + &sky.relative_intensities, + &mut eg, + ) + } None => ExpectedIntensities { precursor_intensities: eg.iter_precursors().map(|(idx, _mz)| (idx, 1.0)).collect(), fragment_intensities: eg diff --git a/rust/timsseek/src/data_sources/mod.rs b/rust/timsseek/src/data_sources/mod.rs index a3af6d5..b2f0e0f 100644 --- a/rust/timsseek/src/data_sources/mod.rs +++ b/rust/timsseek/src/data_sources/mod.rs @@ -1,3 +1,7 @@ pub mod speclib; +pub use speclib::PrecursorEntry; +pub use speclib::ReferenceEG; +pub use speclib::SerSpeclibElement; pub use speclib::Speclib; +pub use speclib::SpeclibWriter; diff --git a/rust/timsseek/src/data_sources/speclib.rs b/rust/timsseek/src/data_sources/speclib.rs index a6094c4..95fde31 100644 --- a/rust/timsseek/src/data_sources/speclib.rs +++ b/rust/timsseek/src/data_sources/speclib.rs @@ -29,6 +29,7 @@ use timsquery::serde::{ DiannPrecursorExtras, ElutionGroupCollection, FileReadingExtras, + SkylinePrecursorExtras, read_library_file as read_timsquery_library, }; @@ -41,6 +42,13 @@ pub struct SerSpeclibElement { } impl SerSpeclibElement { + pub fn new(precursor: PrecursorEntry, elution_group: ReferenceEG) -> Self { + Self { + precursor, + elution_group, + } + } + pub fn sample() -> Self { SerSpeclibElement { precursor: PrecursorEntry { @@ -91,13 +99,24 @@ impl SerSpeclibElement { } #[derive(Debug, Clone, Serialize, Deserialize)] -struct PrecursorEntry { +pub struct PrecursorEntry { sequence: String, charge: u8, decoy: bool, decoy_group: u32, } +impl PrecursorEntry { + pub fn new(sequence: String, charge: u8, decoy: bool, decoy_group: u32) -> Self { + Self { + sequence, + charge, + decoy, + decoy_group, + } + } +} + impl From for DigestSlice { fn from(x: PrecursorEntry) -> Self { let decoy = if x.decoy { @@ -164,6 +183,32 @@ pub struct ReferenceEG { rt_seconds: f32, } +impl ReferenceEG { + pub fn new( + id: u32, + precursor_mz: f64, + precursor_labels: Vec, + fragment_mzs: Vec, + fragment_labels: Vec, + precursor_intensities: Vec, + fragment_intensities: Vec, + mobility_ook0: f32, + rt_seconds: f32, + ) -> Self { + Self { + id, + precursor_mz, + precursor_labels, + fragment_mzs, + fragment_labels, + precursor_intensities, + fragment_intensities, + mobility_ook0, + rt_seconds, + } + } +} + /// Convert a DIA-NN library entry to a QueryItemToScore /// /// This handles conversion from TimsElutionGroup + DiannPrecursorExtras @@ -295,6 +340,19 @@ fn create_mass_shifted_decoy( }) } +/// `SkylinePrecursorExtras` is structurally identical to `DiannPrecursorExtras` +/// (peptide + protein + decoy flag + fragment intensity pairs), so we adapt it +/// into the DIA-NN shape and reuse `convert_diann_to_query_item`. +fn skyline_to_diann_extras(sky: SkylinePrecursorExtras) -> DiannPrecursorExtras { + DiannPrecursorExtras { + modified_peptide: sky.modified_peptide, + stripped_peptide: sky.stripped_peptide, + protein_id: sky.protein_id, + is_decoy: sky.is_decoy, + relative_intensities: sky.relative_intensities, + } +} + /// Convert an ElutionGroupCollection from timsquery to a Speclib (without applying strategy) fn convert_elution_group_collection( collection: ElutionGroupCollection, @@ -329,6 +387,35 @@ fn convert_elution_group_collection( Ok(Speclib { elems }) } + ElutionGroupCollection::MzpafLabels(egs, Some(FileReadingExtras::Skyline(extras))) => { + if egs.len() != extras.len() { + return Err(LibraryReadingError::UnsupportedFormat { + message: format!( + "Mismatch between Skyline elution groups ({}) and extras ({})", + egs.len(), + extras.len() + ), + }); + } + + tracing::info!( + "Converting {} Skyline transition-list entries to Speclib format", + egs.len() + ); + + let elems: Vec<_> = egs + .into_iter() + .zip(extras) + .enumerate() + .map(|(idx, (eg, sky_extra))| { + let decoy_group = idx as u32; + let diann_extra = skyline_to_diann_extras(sky_extra); + convert_diann_to_query_item(eg, diann_extra, decoy_group) + }) + .collect::, _>>()?; + + Ok(Speclib { elems }) + } ElutionGroupCollection::MzpafLabels(_, None) => { Err(LibraryReadingError::UnsupportedFormat { message: "MzpafLabels variant without DiannPrecursorExtras is not supported" @@ -338,7 +425,7 @@ fn convert_elution_group_collection( _ => { tracing::warn!("Unsupported ElutionGroupCollection variant for timsseek"); Err(LibraryReadingError::UnsupportedFormat { - message: "Only DIA-NN TSV/Parquet libraries are currently supported via timsquery" + message: "Only DIA-NN TSV/Parquet and Skyline CSV libraries are currently supported via timsquery" .to_string(), }) } @@ -765,6 +852,46 @@ impl Speclib { } } +pub struct SpeclibWriter { + inner: SpeclibWriterInner, +} + +enum SpeclibWriterInner { + MsgpackZstd(zstd::Encoder<'static, W>), +} + +impl SpeclibWriter { + pub fn new_msgpack_zstd(writer: W) -> Result { + let encoder = zstd::Encoder::new(writer, 3)?; + Ok(Self { + inner: SpeclibWriterInner::MsgpackZstd(encoder), + }) + } + + pub fn append(&mut self, elem: &SerSpeclibElement) -> Result<(), LibraryReadingError> { + match &mut self.inner { + SpeclibWriterInner::MsgpackZstd(encoder) => { + rmp_serde::encode::write(encoder, elem).map_err(|e| { + LibraryReadingError::SpeclibParsingError { + source: serde_json::Error::io(std::io::Error::new( + std::io::ErrorKind::InvalidData, + e, + )), + context: "Error writing MessagePack", + } + })?; + } + } + Ok(()) + } + + pub fn finish(self) -> Result { + match self.inner { + SpeclibWriterInner::MsgpackZstd(encoder) => encoder.finish(), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -911,6 +1038,47 @@ mod tests { ); } + #[test] + fn test_load_skyline_csv_library() { + let test_file = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("timsquery") + .join("tests") + .join("skyline_io_files") + .join("sample_transition_list.csv"); + + assert!( + test_file.exists(), + "Test file should exist at {:?}", + test_file + ); + + let speclib = Speclib::from_file(&test_file, crate::models::DecoyStrategy::default()) + .expect("Failed to load Skyline CSV library"); + + // Fixture has 14 PRTC targets, no decoys -> 14 targets + 28 mass-shift decoys + assert_eq!(speclib.len(), 42, "Expected 42 entries (14 targets + 28 decoys)"); + + let n_targets = speclib.elems.iter().filter(|e| !e.digest.is_decoy()).count(); + let n_decoys = speclib.elems.iter().filter(|e| e.digest.is_decoy()).count(); + assert_eq!(n_targets, 14, "Should have 14 targets"); + assert_eq!(n_decoys, 28, "Should have 28 decoys"); + + // Isotope envelope should have been attached (3 isotopes) for every target + for entry in speclib.elems.iter().filter(|e| !e.digest.is_decoy()) { + assert_eq!( + entry.query.iter_precursors().count(), + 3, + "Each target should have 3 isotopes in precursor envelope" + ); + assert!( + entry.query.fragment_count() > 0, + "Each target should have at least one fragment" + ); + } + } + #[test] fn test_load_diann_txt_library() { // Use the test file from timsquery tests @@ -1215,4 +1383,47 @@ mod tests { } } } + + #[test] + fn test_ser_speclib_element_roundtrip() { + let elem = SerSpeclibElement::new( + PrecursorEntry::new("PEPTIDEK".to_string(), 2, false, 0), + ReferenceEG::new( + 0, + 500.0, + vec![0, 1, 2], + vec![300.0, 400.0], + vec![ + IonAnnot::try_from("y1").unwrap(), + IonAnnot::try_from("y2").unwrap(), + ], + vec![1.0, 0.5, 0.2], + vec![0.8, 0.3], + 0.75, + 120.0, + ), + ); + let bytes = rmp_serde::to_vec(&elem).unwrap(); + let decoded: SerSpeclibElement = rmp_serde::from_slice(&bytes).unwrap(); + let qi: QueryItemToScore = decoded.into(); + assert_eq!(qi.digest.len(), "PEPTIDEK".len()); + assert_eq!(qi.query.fragment_count(), 2); + } + + #[test] + fn test_speclib_writer_roundtrip() { + let elem = SerSpeclibElement::sample(); + let mut buf = Vec::new(); + { + let mut writer = SpeclibWriter::new_msgpack_zstd(&mut buf).unwrap(); + writer.append(&elem).unwrap(); + writer.append(&elem).unwrap(); + writer.finish().unwrap(); + } + let reader = + SpeclibReader::new(std::io::Cursor::new(&buf), SpeclibFormat::MessagePackZstd) + .unwrap(); + let items: Vec<_> = reader.collect::, _>>().unwrap(); + assert_eq!(items.len(), 2); + } } diff --git a/rust/timsseek/src/models/digest.rs b/rust/timsseek/src/models/digest.rs index 98f99f3..df08d51 100644 --- a/rust/timsseek/src/models/digest.rs +++ b/rust/timsseek/src/models/digest.rs @@ -78,6 +78,14 @@ impl DigestSlice { self.range.is_empty() } + /// Returns the peptide sequence as a string slice without allocation. + /// For Target and ReversedDecoy, returns the raw slice. + /// For NonReversedDecoy, this returns the ORIGINAL (non-reversed) sequence — + /// use `Into::` if you need the decoy form. + pub fn as_str(&self) -> &str { + &self.ref_seq[self.range.start as usize..self.range.end as usize] + } + pub fn is_decoy(&self) -> bool { matches!( self.decoy, @@ -158,6 +166,14 @@ mod tests { assert_eq!(deduped[1].len(), seq2.as_ref().len()); } + #[test] + fn test_as_str() { + let seq: Arc = "PEPTIDEPINKTOMATO".into(); + let slice = DigestSlice::new(seq.clone(), 0..7, DecoyMarking::Target, 0); + assert_eq!(slice.as_str(), "PEPTIDE"); + assert_eq!(slice.as_str().as_bytes(), b"PEPTIDE"); + } + #[test] fn test_from_string() { let seq = "PEPTIDEPINKTOMATO".to_string(); diff --git a/uv.lock b/uv.lock index 9b0cff1..e4d7198 100644 --- a/uv.lock +++ b/uv.lock @@ -16,183 +16,6 @@ resolution-markers = [ "python_full_version < '3.12' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -[manifest] -members = [ - "speclib-builder", - "timsseek-workspace", -] - -[[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 = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "appnope" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, -] - -[[package]] -name = "argon2-cffi" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "argon2-cffi-bindings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, -] - -[[package]] -name = "argon2-cffi-bindings" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, - { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, - { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, -] - -[[package]] -name = "arrow" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, -] - -[[package]] -name = "asttokens" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, -] - -[[package]] -name = "async-lru" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, -] - -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - -[[package]] -name = "babel" -version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, -] - -[[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, -] - -[[package]] -name = "bleach" -version = "6.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, -] - -[package.optional-dependencies] -css = [ - { name = "tinycss2" }, -] - -[[package]] -name = "boto3" -version = "1.42.88" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, - { name = "jmespath" }, - { name = "s3transfer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/bb/7d4435cca6fccf235dd40c891c731bcb9078e815917b57ebadd1e0ffabaf/boto3-1.42.88.tar.gz", hash = "sha256:2d22c70de5726918676a06f1a03acfb4d5d9ea92fc759354800b67b22aaeef19", size = 113238, upload-time = "2026-04-10T19:41:06.912Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/2b/8bfddb39a19f5fbc16a869f1a394771e6223f07160dbc0ff6b38e05ea0ae/boto3-1.42.88-py3-none-any.whl", hash = "sha256:2d0f52c971503377e4370d2a83edee6f077ddb8e684366ff38df4f13581d9cfc", size = 140557, upload-time = "2026-04-10T19:41:05.309Z" }, -] - -[[package]] -name = "botocore" -version = "1.42.88" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jmespath" }, - { name = "python-dateutil" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/50/87966238f7aa3f7e5f87081185d5a407a95ede8b551e11bbe134ca3306dc/botocore-1.42.88.tar.gz", hash = "sha256:cbb59ee464662039b0c2c95a520cdf85b1e8ce00b72375ab9cd9f842cc001301", size = 15195331, upload-time = "2026-04-10T19:40:57.012Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/46/ad14e41245adb8b0c83663ba13e822b68a0df08999dd250e75b0750fdf6c/botocore-1.42.88-py3-none-any.whl", hash = "sha256:032375b213305b6b81eedb269eaeefdf96f674620799bbf96117dca86052cc1a", size = 14876640, upload-time = "2026-04-10T19:40:53.663Z" }, -] - [[package]] name = "bumpver" version = "2025.1131" @@ -208,51 +31,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/5b/2d5ea6802495ee4506721977be522804314aa66ad629d9356e3c7e5af4a6/bumpver-2025.1131-py2.py3-none-any.whl", hash = "sha256:c02527f6ed7887afbc06c07630047b24a9f9d02d544a65639e99bf8b92aaa674", size = 65361, upload-time = "2025-07-02T20:36:10.103Z" }, ] -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, -] - [[package]] name = "cfgv" version = "3.5.0" @@ -262,47 +40,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] -[[package]] -name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, - { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, - { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, - { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, - { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, - { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, - { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, - { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, -] - [[package]] name = "click" version = "8.3.2" @@ -315,20 +52,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, ] -[[package]] -name = "cloudpathlib" -version = "0.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126, upload-time = "2025-10-07T22:47:56.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, -] - -[package.optional-dependencies] -s3 = [ - { name = "boto3" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -338,15 +61,6 @@ 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 = "comm" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, -] - [[package]] name = "contourpy" version = "1.3.3" @@ -394,41 +108,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] -[[package]] -name = "debugpy" -version = "1.8.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, - { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, - { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, - { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, - { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, - { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, -] - -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, -] - -[[package]] -name = "defusedxml" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, -] - [[package]] name = "distlib" version = "0.4.0" @@ -438,72 +117,6 @@ 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 = "elfragmentadonnx" -version = "0.23.0" -source = { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentadonnx-0.23.0-py3-none-any.whl" } -dependencies = [ - { name = "elfragmentador-core" }, - { name = "numpy" }, - { name = "onnx" }, - { name = "onnxruntime" }, - { name = "onnxscript" }, - { name = "rich" }, - { name = "rustyms" }, -] -wheels = [ - { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentadonnx-0.23.0-py3-none-any.whl", hash = "sha256:fe01a8823cee34849ac71f011299381360946cb83165b69f5f7e1a48851f819d" }, -] - -[package.metadata] -requires-dist = [ - { name = "elfragmentador-core" }, - { name = "numpy", specifier = ">=2.0,<3.0" }, - { name = "onnx", specifier = ">=1.17.0,<2.0.0" }, - { name = "onnxruntime", specifier = ">=1.16.0,<2.0.0" }, - { name = "onnxscript", specifier = ">=0.1.0.dev20241112" }, - { name = "rich", specifier = ">=13.9.4" }, - { name = "rustyms", specifier = ">=0.8.3,<0.9.0" }, -] - -[[package]] -name = "elfragmentador-core" -version = "0.23.0" -source = { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentador_core-0.23.0-py3-none-any.whl" } -dependencies = [ - { name = "numpy" }, - { name = "requests" }, - { name = "rustyms" }, -] -wheels = [ - { url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentador_core-0.23.0-py3-none-any.whl", hash = "sha256:45a133beef70e121ba9950d0456a34c757750edbd750bf021e3e287b656ebcea" }, -] - -[package.metadata] -requires-dist = [ - { name = "numpy", specifier = ">=2.0,<3.0" }, - { name = "requests", specifier = ">=2.32.3,<3.0" }, - { name = "rustyms", specifier = ">=0.8.3,<0.9.0" }, -] - -[[package]] -name = "executing" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, -] - -[[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.25.2" @@ -513,14 +126,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] -[[package]] -name = "flatbuffers" -version = "25.12.19" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, -] - [[package]] name = "fonttools" version = "4.62.1" @@ -536,503 +141,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, -] - -[[package]] -name = "fqdn" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "identify" -version = "2.6.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[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 = "ipykernel" -version = "7.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appnope", marker = "sys_platform == 'darwin'" }, - { name = "comm" }, - { name = "debugpy" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "matplotlib-inline" }, - { name = "nest-asyncio" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, -] - -[[package]] -name = "ipython" -version = "9.10.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12' and platform_machine != 's390x' and sys_platform == 'win32'", - "python_full_version < '3.12' and platform_machine == 's390x' and sys_platform == 'win32'", - "python_full_version < '3.12' and platform_machine != 's390x' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and platform_machine == 's390x' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and platform_machine != 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version < '3.12' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" }, - { name = "jedi", marker = "python_full_version < '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.12'" }, - { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.12'" }, - { name = "pygments", marker = "python_full_version < '3.12'" }, - { name = "stack-data", marker = "python_full_version < '3.12'" }, - { name = "traitlets", marker = "python_full_version < '3.12'" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232", size = 622827, upload-time = "2026-03-27T09:53:24.566Z" }, -] - -[[package]] -name = "ipython" -version = "9.12.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and platform_machine != 's390x' and sys_platform == 'win32'", - "python_full_version >= '3.12' and platform_machine == 's390x' and sys_platform == 'win32'", - "python_full_version >= '3.12' and platform_machine != 's390x' and sys_platform == 'emscripten'", - "python_full_version >= '3.12' and platform_machine == 's390x' and sys_platform == 'emscripten'", - "python_full_version >= '3.12' and platform_machine != 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version >= '3.12' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, - { name = "jedi", marker = "python_full_version >= '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, - { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "stack-data", marker = "python_full_version >= '3.12'" }, - { name = "traitlets", marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" }, -] - -[[package]] -name = "ipython-pygments-lexers" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, -] - -[[package]] -name = "ipywidgets" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "comm" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "jupyterlab-widgets" }, - { name = "traitlets" }, - { name = "widgetsnbextension" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, -] - -[[package]] -name = "isoduration" -version = "20.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "arrow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, -] - -[[package]] -name = "jedi" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "parso" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, -] - -[[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 = "jmespath" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, -] - -[[package]] -name = "json5" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/6f8906aaf67d501e259b0adab4d312945bb7211e8b8d4dcc77c92320edaa/json5-0.14.0.tar.gz", hash = "sha256:b3f492fad9f6cdbced8b7d40b28b9b1c9701c5f561bef0d33b81c2ff433fefcb", size = 52656, upload-time = "2026-03-27T22:50:48.108Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl", hash = "sha256:56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a", size = 36271, upload-time = "2026-03-27T22:50:47.073Z" }, -] - -[[package]] -name = "jsonpointer" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[package.optional-dependencies] -format-nongpl = [ - { name = "fqdn" }, - { name = "idna" }, - { name = "isoduration" }, - { name = "jsonpointer" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "rfc3987-syntax" }, - { name = "uri-template" }, - { name = "webcolors" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, -] - -[[package]] -name = "jupyter" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipywidgets" }, - { name = "jupyter-console" }, - { name = "jupyterlab" }, - { name = "nbconvert" }, - { name = "notebook" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959, upload-time = "2024-08-30T07:15:48.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" }, -] - -[[package]] -name = "jupyter-client" -version = "8.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-core" }, - { name = "python-dateutil" }, - { name = "pyzmq" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, -] - -[[package]] -name = "jupyter-console" -version = "6.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ipykernel" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "pyzmq" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363, upload-time = "2023-03-06T14:13:31.02Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510, upload-time = "2023-03-06T14:13:28.229Z" }, -] - -[[package]] -name = "jupyter-core" -version = "5.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, -] - -[[package]] -name = "jupyter-events" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema", extra = ["format-nongpl"] }, - { name = "packaging" }, - { name = "python-json-logger" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, -] - -[[package]] -name = "jupyter-lsp" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/ff/1e4a61f5170a9a1d978f3ac3872449de6c01fc71eaf89657824c878b1549/jupyter_lsp-2.3.1.tar.gz", hash = "sha256:fdf8a4aa7d85813976d6e29e95e6a2c8f752701f926f2715305249a3829805a6", size = 55677, upload-time = "2026-04-02T08:10:06.749Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/e8/9d61dcbd1dce8ef418f06befd4ac084b4720429c26b0b1222bc218685eff/jupyter_lsp-2.3.1-py3-none-any.whl", hash = "sha256:71b954d834e85ff3096400554f2eefaf7fe37053036f9a782b0f7c5e42dadb81", size = 77513, upload-time = "2026-04-02T08:10:01.753Z" }, -] - -[[package]] -name = "jupyter-server" -version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "argon2-cffi" }, - { name = "jinja2" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "jupyter-events" }, - { name = "jupyter-server-terminals" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "overrides", marker = "python_full_version < '3.12'" }, - { name = "packaging" }, - { name = "prometheus-client" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "pyzmq" }, - { name = "send2trash" }, - { name = "terminado" }, - { name = "tornado" }, - { name = "traitlets" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, -] - -[[package]] -name = "jupyter-server-terminals" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "terminado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, -] - -[[package]] -name = "jupyterlab" -version = "4.5.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-lru" }, - { name = "httpx" }, - { name = "ipykernel" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyter-lsp" }, - { name = "jupyter-server" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "packaging" }, - { name = "setuptools" }, - { name = "tornado" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, -] - -[[package]] -name = "jupyterlab-pygments" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] -name = "jupyterlab-server" -version = "2.28.0" +name = "identify" +version = "2.6.18" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "babel" }, - { name = "jinja2" }, - { name = "json5" }, - { name = "jsonschema" }, - { name = "jupyter-server" }, - { name = "packaging" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, ] [[package]] -name = "jupyterlab-widgets" -version = "3.0.16" +name = "iniconfig" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } +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/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, + { 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]] @@ -1082,15 +216,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] -[[package]] -name = "lark" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, -] - [[package]] name = "lexid" version = "2021.1006" @@ -1100,61 +225,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/e3/35764404a4b7e2021be1f88f42264c2e92e0c4720273559a62461ce64a47/lexid-2021.1006-py2.py3-none-any.whl", hash = "sha256:5526bb5606fd74c7add23320da5f02805bddd7c77916f2dc1943e6bada8605ed", size = 7587, upload-time = "2021-04-02T20:18:33.129Z" }, ] -[[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 = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, -] - [[package]] name = "matplotlib" version = "3.10.8" @@ -1191,36 +261,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] -[[package]] -name = "matplotlib-inline" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "mistune" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, -] - [[package]] name = "mizani" version = "0.14.4" @@ -1236,126 +276,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/30/b6617c74a8234ff60265373ef730eb6378ccdda74042f51f9ac936191664/mizani-0.14.4-py3-none-any.whl", hash = "sha256:ed72bf249e2a18b5dcc65cd54c7eaa5444b2cb09c7e18aafa2ab6f05f1b78620", size = 133471, upload-time = "2026-01-28T14:42:16.328Z" }, ] -[[package]] -name = "ml-dtypes" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, - { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, -] - -[[package]] -name = "msgpack" -version = "1.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, - { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, - { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, - { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, -] - -[[package]] -name = "nbclient" -version = "0.10.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "nbformat" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, -] - -[[package]] -name = "nbconvert" -version = "7.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "bleach", extra = ["css"] }, - { name = "defusedxml" }, - { name = "jinja2" }, - { name = "jupyter-core" }, - { name = "jupyterlab-pygments" }, - { name = "markupsafe" }, - { name = "mistune" }, - { name = "nbclient" }, - { name = "nbformat" }, - { name = "packaging" }, - { name = "pandocfilters" }, - { name = "pygments" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" }, -] - -[[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 = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] - [[package]] name = "nodeenv" version = "1.10.0" @@ -1365,34 +285,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] -[[package]] -name = "notebook" -version = "7.5.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, - { name = "jupyterlab" }, - { name = "jupyterlab-server" }, - { name = "notebook-shim" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1f/6d/41052c48d6f6349ca0a7c4d1f6a78464de135e6d18f5829ba2510e62184c/notebook-7.5.5.tar.gz", hash = "sha256:dc0bfab0f2372c8278c457423d3256c34154ac2cc76bf20e9925260c461013c3", size = 14169167, upload-time = "2026-03-11T16:32:51.922Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl", hash = "sha256:a7c14dbeefa6592e87f72290ca982e0c10f5bbf3786be2a600fda9da2764a2b8", size = 14578929, upload-time = "2026-03-11T16:32:48.021Z" }, -] - -[[package]] -name = "notebook-shim" -version = "0.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, -] - [[package]] name = "numpy" version = "2.4.4" @@ -1430,98 +322,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, ] -[[package]] -name = "onnx" -version = "1.21.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/93/942d2a0f6a70538eea042ce0445c8aefd46559ad153469986f29a743c01c/onnx-1.21.0.tar.gz", hash = "sha256:4d8b67d0aaec5864c87633188b91cc520877477ec0254eda122bef8be43cd764", size = 12074608, upload-time = "2026-03-27T21:33:36.118Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/48/32e383aa6bc40b72a9fd419937aaa647078190c9bfccdc97b316d2dee687/onnx-1.21.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:2aca19949260875c14866fc77ea0bc37e4e809b24976108762843d328c92d3ce", size = 17968053, upload-time = "2026-03-27T21:32:29.558Z" }, - { url = "https://files.pythonhosted.org/packages/e2/26/5726e8df7d36e96bb3c679912d1a86af42f393d77aa17d6b98a97d4289ce/onnx-1.21.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82aa6ab51144df07c58c4850cb78d4f1ae969d8c0bf657b28041796d49ba6974", size = 17534821, upload-time = "2026-03-27T21:32:32.351Z" }, - { url = "https://files.pythonhosted.org/packages/d6/2b/021dcd2dd50c3c71b7959d7368526da384a295c162fb4863f36057973f78/onnx-1.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c3185a232089335581fabb98fba4e86d3e8246b8140f2e406082438100ebda", size = 17616664, upload-time = "2026-03-27T21:32:34.921Z" }, - { url = "https://files.pythonhosted.org/packages/12/00/afa32a46fa122a7ed42df1cfe8796922156a3725ba8fc581c4779c96e2fc/onnx-1.21.0-cp311-cp311-win32.whl", hash = "sha256:f53b3c15a3b539c16b99655c43c365622046d68c49b680c48eba4da2a4fb6f27", size = 16289035, upload-time = "2026-03-27T21:32:37.783Z" }, - { url = "https://files.pythonhosted.org/packages/73/8d/483cc980a24d4c0131d0af06d0ff6a37fb08ae90a7848ece8cef645194f1/onnx-1.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:5f78c411743db317a76e5d009f84f7e3d5380411a1567a868e82461a1e5c775d", size = 16443748, upload-time = "2026-03-27T21:32:40.337Z" }, - { url = "https://files.pythonhosted.org/packages/38/78/9d06fd5aaaed1ec9cb8a3b70fbbf00c1bdc18db610771e96379f0ed58112/onnx-1.21.0-cp311-cp311-win_arm64.whl", hash = "sha256:ab6a488dabbb172eebc9f3b3e7ac68763f32b0c571626d4a5004608f866cc83d", size = 16406123, upload-time = "2026-03-27T21:32:45.159Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ae/cb644ec84c25e63575d9d8790fdcc5d1a11d67d3f62f872edb35fa38d158/onnx-1.21.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:fc2635400fe39ff37ebc4e75342cc54450eadadf39c540ff132c319bf4960095", size = 17965930, upload-time = "2026-03-27T21:32:48.089Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b6/eeb5903586645ef8a49b4b7892580438741acc3df91d7a5bd0f3a59ea9cb/onnx-1.21.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9003d5206c01fa2ff4b46311566865d8e493e1a6998d4009ec6de39843f1b59b", size = 17531344, upload-time = "2026-03-27T21:32:50.837Z" }, - { url = "https://files.pythonhosted.org/packages/a7/00/4823f06357892d1e60d6f34e7299d2ba4ed2108c487cc394f7ce85a3ff14/onnx-1.21.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9261bd580fb8548c9c37b3c6750387eb8f21ea43c63880d37b2c622e1684285", size = 17613697, upload-time = "2026-03-27T21:32:54.222Z" }, - { url = "https://files.pythonhosted.org/packages/23/1d/391f3c567ae068c8ac4f1d1316bae97c9eb45e702f05975fe0e17ad441f0/onnx-1.21.0-cp312-abi3-win32.whl", hash = "sha256:9ea4e824964082811938a9250451d89c4ec474fe42dd36c038bfa5df31993d1e", size = 16287200, upload-time = "2026-03-27T21:32:57.277Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a6/5eefbe5b40ea96de95a766bd2e0e751f35bdea2d4b951991ec9afaa69531/onnx-1.21.0-cp312-abi3-win_amd64.whl", hash = "sha256:458d91948ad9a7729a347550553b49ab6939f9af2cddf334e2116e45467dc61f", size = 16441045, upload-time = "2026-03-27T21:33:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/63/c4/0ed8dc037a39113d2a4d66e0005e07751c299c46b993f1ad5c2c35664c20/onnx-1.21.0-cp312-abi3-win_arm64.whl", hash = "sha256:ca14bc4842fccc3187eb538f07eabeb25a779b39388b006db4356c07403a7bbb", size = 16403134, upload-time = "2026-03-27T21:33:03.987Z" }, -] - -[[package]] -name = "onnx-ir" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "onnx" }, - { name = "sympy" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/a5/acc43c8fa6edbc584d127fb6bbd13ae9ebfc01b9675c74e0da2de15fa4a6/onnx_ir-0.2.0.tar.gz", hash = "sha256:8bad3906691987290789b26d05e0dbff467029a0b1e411e12e4cae02e43503e4", size = 141693, upload-time = "2026-02-24T02:31:10.998Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/df/a99736bcca6b16e36c687ce4996abcf4ce73c514fddd9e730cfcb6a334f2/onnx_ir-0.2.0-py3-none-any.whl", hash = "sha256:eb14d1399c2442bd1ff702719e70074e9cedfa3af5729416a32752c9e0f82591", size = 164100, upload-time = "2026-02-24T02:31:09.454Z" }, -] - -[[package]] -name = "onnxruntime" -version = "1.24.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/69/6c40720201012c6af9aa7d4ecdd620e521bd806dc6269d636fdd5c5aeebe/onnxruntime-1.24.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bdfce8e9a6497cec584aab407b71bf697dac5e1b7b7974adc50bf7533bdb3a2", size = 17332131, upload-time = "2026-03-17T22:05:49.005Z" }, - { url = "https://files.pythonhosted.org/packages/38/e9/8c901c150ce0c368da38638f44152fb411059c0c7364b497c9e5c957321a/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:046ff290045a387676941a02a8ae5c3ebec6b4f551ae228711968c4a69d8f6b7", size = 15152472, upload-time = "2026-03-17T22:03:26.176Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b6/7a4df417cdd01e8f067a509e123ac8b31af450a719fa7ed81787dd6057ec/onnxruntime-1.24.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e54ad52e61d2d4618dcff8fa1480ac66b24ee2eab73331322db1049f11ccf330", size = 17222993, upload-time = "2026-03-17T22:04:34.485Z" }, - { url = "https://files.pythonhosted.org/packages/dd/59/8febe015f391aa1757fa5ba82c759ea4b6c14ef970132efb5e316665ba61/onnxruntime-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b43b63eb24a2bc8fc77a09be67587a570967a412cccb837b6245ccb546691153", size = 12594863, upload-time = "2026-03-17T22:05:38.749Z" }, - { url = "https://files.pythonhosted.org/packages/32/84/4155fcd362e8873eb6ce305acfeeadacd9e0e59415adac474bea3d9281bb/onnxruntime-1.24.4-cp311-cp311-win_arm64.whl", hash = "sha256:e26478356dba25631fb3f20112e345f8e8bf62c499bb497e8a559f7d69cf7e7b", size = 12259895, upload-time = "2026-03-17T22:05:28.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875, upload-time = "2026-03-17T22:05:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485, upload-time = "2026-03-17T22:03:32.182Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912, upload-time = "2026-03-17T22:04:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856, upload-time = "2026-03-17T22:05:41.224Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275, upload-time = "2026-03-17T22:05:31.132Z" }, -] - -[[package]] -name = "onnxscript" -version = "0.6.3.dev20260411" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "onnx" }, - { name = "onnx-ir" }, - { name = "packaging" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/77/86/7d1ad52e8c1439b3455b1451fb237b2689aaa9df592d05d9788cee9f3f99/onnxscript-0.6.3.dev20260411.tar.gz", hash = "sha256:f0350d529df820d463b5b50a97bda05bebb7e4fa766bde75362309ea90a772a8", size = 609333, upload-time = "2026-04-11T07:23:50.8Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/00/8cc5d4c5fbdd0691bc5a0d578fa5fc818170eb6cef407224aa57cc7d29c3/onnxscript-0.6.3.dev20260411-py3-none-any.whl", hash = "sha256:af8b6074d3f886f5e0dbbca83436988e744c0732431b899133695a599d99bb78", size = 712093, upload-time = "2026-04-11T07:23:53.018Z" }, -] - -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, -] - [[package]] name = "packaging" version = "26.0" @@ -1560,24 +360,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118, upload-time = "2026-03-31T06:46:54.548Z" }, ] -[[package]] -name = "pandocfilters" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, -] - -[[package]] -name = "parso" -version = "0.8.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, -] - [[package]] name = "patsy" version = "1.0.2" @@ -1590,18 +372,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" }, ] -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, -] - [[package]] name = "pillow" version = "12.2.0" @@ -1671,200 +441,51 @@ 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 = "polars" -version = "1.39.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "polars-runtime-32" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/ab/f19e592fce9e000da49c96bf35e77cef67f9cb4b040bfa538a2764c0263e/polars-1.39.3.tar.gz", hash = "sha256:2e016c7f3e8d14fa777ef86fe0477cec6c67023a20ba4c94d6e8431eefe4a63c", size = 728987, upload-time = "2026-03-20T11:16:24.836Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/db/08f4ca10c5018813e7e0b59e4472302328b3d2ab1512f5a2157a814540e0/polars-1.39.3-py3-none-any.whl", hash = "sha256:c2b955ccc0a08a2bc9259785decf3d5c007b489b523bf2390cf21cec2bb82a56", size = 823985, upload-time = "2026-03-20T11:14:23.619Z" }, -] - -[[package]] -name = "polars-runtime-32" -version = "1.39.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/39/c8688696bc22b6c501e3b82ef3be10e543c07a785af5660f30997cd22dd2/polars_runtime_32-1.39.3.tar.gz", hash = "sha256:c728e4f469cafab501947585f36311b8fb222d3e934c6209e83791e0df20b29d", size = 2872335, upload-time = "2026-03-20T11:16:26.581Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/74/1b41205f7368c9375ab1dea91178eaa20435fe3eff036390a53a7660b416/polars_runtime_32-1.39.3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:425c0b220b573fa097b4042edff73114cc6d23432a21dfd2dc41adf329d7d2e9", size = 45273243, upload-time = "2026-03-20T11:14:26.691Z" }, - { url = "https://files.pythonhosted.org/packages/90/bf/297716b3095fe719be20fcf7af1d2b6ab069c38199bbace2469608a69b3a/polars_runtime_32-1.39.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef5884711e3c617d7dc93519a7d038e242f5741cfe5fe9afd32d58845d86c562", size = 40842924, upload-time = "2026-03-20T11:14:31.154Z" }, - { url = "https://files.pythonhosted.org/packages/3d/3e/e65236d9d0d9babfa0ecba593413c06530fca60a8feb8f66243aa5dba92e/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06b47f535eb1f97a9a1e5b0053ef50db3a4276e241178e37bbb1a38b1fa53b14", size = 43220650, upload-time = "2026-03-20T11:14:35.458Z" }, - { url = "https://files.pythonhosted.org/packages/b0/15/fc3e43f3fdf3f20b7dfb5abe871ab6162cf8fb4aeabf4cfad822d5dc4c79/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc9e13dc1d2e828331f2fe8ccbc9757554dc4933a8d3e85e906b988178f95ed", size = 46877498, upload-time = "2026-03-20T11:14:40.14Z" }, - { url = "https://files.pythonhosted.org/packages/3c/81/bd5f895919e32c6ab0a7786cd0c0ca961cb03152c47c3645808b54383f31/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:363d49e3a3e638fc943e2b9887940300a7d06789930855a178a4727949259dc2", size = 43380176, upload-time = "2026-03-20T11:14:45.566Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3e/c86433c3b5ec0315bdfc7640d0c15d41f1216c0103a0eab9a9b5147d6c4c/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7c206bdcc7bc62ea038d6adea8e44b02f0e675e0191a54c810703b4895208ea4", size = 46485933, upload-time = "2026-03-20T11:14:51.155Z" }, - { url = "https://files.pythonhosted.org/packages/54/ce/200b310cf91f98e652eb6ea09fdb3a9718aa0293ebf113dce325797c8572/polars_runtime_32-1.39.3-cp310-abi3-win_amd64.whl", hash = "sha256:d66ca522517554a883446957539c40dc7b75eb0c2220357fb28bc8940d305339", size = 46995458, upload-time = "2026-03-20T11:14:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/da/76/2d48927e0aa2abbdde08cbf4a2536883b73277d47fbeca95e952de86df34/polars_runtime_32-1.39.3-cp310-abi3-win_arm64.whl", hash = "sha256:f49f51461de63f13e5dd4eb080421c8f23f856945f3f8bd5b2b1f59da52c2860", size = 41857648, upload-time = "2026-03-20T11:15:01.142Z" }, -] - -[[package]] -name = "pre-commit" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, -] - -[[package]] -name = "prometheus-client" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - -[[package]] -name = "protobuf" -version = "7.34.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" }, - { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" }, - { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" }, - { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" }, - { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" }, - { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" }, - { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" }, -] - -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, + { 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 = "pycparser" -version = "3.0" +name = "polars" +version = "1.39.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/ab/f19e592fce9e000da49c96bf35e77cef67f9cb4b040bfa538a2764c0263e/polars-1.39.3.tar.gz", hash = "sha256:2e016c7f3e8d14fa777ef86fe0477cec6c67023a20ba4c94d6e8431eefe4a63c", size = 728987, upload-time = "2026-03-20T11:16:24.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/b4/db/08f4ca10c5018813e7e0b59e4472302328b3d2ab1512f5a2157a814540e0/polars-1.39.3-py3-none-any.whl", hash = "sha256:c2b955ccc0a08a2bc9259785decf3d5c007b489b523bf2390cf21cec2bb82a56", size = 823985, upload-time = "2026-03-20T11:14:23.619Z" }, ] [[package]] -name = "pydantic" -version = "2.12.5" +name = "polars-runtime-32" +version = "1.39.3" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/17/39/c8688696bc22b6c501e3b82ef3be10e543c07a785af5660f30997cd22dd2/polars_runtime_32-1.39.3.tar.gz", hash = "sha256:c728e4f469cafab501947585f36311b8fb222d3e934c6209e83791e0df20b29d", size = 2872335, upload-time = "2026-03-20T11:16:26.581Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/3b/74/1b41205f7368c9375ab1dea91178eaa20435fe3eff036390a53a7660b416/polars_runtime_32-1.39.3-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:425c0b220b573fa097b4042edff73114cc6d23432a21dfd2dc41adf329d7d2e9", size = 45273243, upload-time = "2026-03-20T11:14:26.691Z" }, + { url = "https://files.pythonhosted.org/packages/90/bf/297716b3095fe719be20fcf7af1d2b6ab069c38199bbace2469608a69b3a/polars_runtime_32-1.39.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef5884711e3c617d7dc93519a7d038e242f5741cfe5fe9afd32d58845d86c562", size = 40842924, upload-time = "2026-03-20T11:14:31.154Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/e65236d9d0d9babfa0ecba593413c06530fca60a8feb8f66243aa5dba92e/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06b47f535eb1f97a9a1e5b0053ef50db3a4276e241178e37bbb1a38b1fa53b14", size = 43220650, upload-time = "2026-03-20T11:14:35.458Z" }, + { url = "https://files.pythonhosted.org/packages/b0/15/fc3e43f3fdf3f20b7dfb5abe871ab6162cf8fb4aeabf4cfad822d5dc4c79/polars_runtime_32-1.39.3-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc9e13dc1d2e828331f2fe8ccbc9757554dc4933a8d3e85e906b988178f95ed", size = 46877498, upload-time = "2026-03-20T11:14:40.14Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/bd5f895919e32c6ab0a7786cd0c0ca961cb03152c47c3645808b54383f31/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:363d49e3a3e638fc943e2b9887940300a7d06789930855a178a4727949259dc2", size = 43380176, upload-time = "2026-03-20T11:14:45.566Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3e/c86433c3b5ec0315bdfc7640d0c15d41f1216c0103a0eab9a9b5147d6c4c/polars_runtime_32-1.39.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7c206bdcc7bc62ea038d6adea8e44b02f0e675e0191a54c810703b4895208ea4", size = 46485933, upload-time = "2026-03-20T11:14:51.155Z" }, + { url = "https://files.pythonhosted.org/packages/54/ce/200b310cf91f98e652eb6ea09fdb3a9718aa0293ebf113dce325797c8572/polars_runtime_32-1.39.3-cp310-abi3-win_amd64.whl", hash = "sha256:d66ca522517554a883446957539c40dc7b75eb0c2220357fb28bc8940d305339", size = 46995458, upload-time = "2026-03-20T11:14:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/da/76/2d48927e0aa2abbdde08cbf4a2536883b73277d47fbeca95e952de86df34/polars_runtime_32-1.39.3-cp310-abi3-win_arm64.whl", hash = "sha256:f49f51461de63f13e5dd4eb080421c8f23f856945f3f8bd5b2b1f59da52c2860", size = 41857648, upload-time = "2026-03-20T11:15:01.142Z" }, ] [[package]] -name = "pydantic-core" -version = "2.41.5" +name = "pre-commit" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] @@ -1885,16 +506,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] -[[package]] -name = "pyteomics" -version = "4.7.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/cd/b7eb951a99ad3ab425a2dbc6e38af076116d13ebcbe144019a2bd37dc093/pyteomics-4.7.5.tar.gz", hash = "sha256:382aeaa8b921bdd2a7e5b4aa9fe46c6184bb43701205a845b4b861ee3e88f46a", size = 236493, upload-time = "2024-10-18T13:26:29.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/59/2ea7637f20a0e2d082fc0cae021d20934aa49ac8154db817a1a3fa9e27b5/pyteomics-4.7.5-py2.py3-none-any.whl", hash = "sha256:9b8008ad8d8bbbc6856c4e804bc88e018df44809cd9a86900862b311e760862d", size = 238983, upload-time = "2024-10-18T13:26:25.923Z" }, - { url = "https://files.pythonhosted.org/packages/fb/62/b5d706255739553398d3a308d92e2476d5a363ad3ca0598c51bc75cc5054/pyteomics-4.7.5-py3-none-any.whl", hash = "sha256:5155e1d2581845926e49b0abd0be8cfd6ea45ffd3511958b805347037c5934c8", size = 238978, upload-time = "2024-10-18T13:26:27.764Z" }, -] - [[package]] name = "pytest" version = "9.0.3" @@ -1936,27 +547,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] -[[package]] -name = "python-json-logger" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, -] - -[[package]] -name = "pywinpty" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/54/37c7370ba91f579235049dc26cd2c5e657d2a943e01820844ffc81f32176/pywinpty-3.0.3.tar.gz", hash = "sha256:523441dc34d231fb361b4b00f8c99d3f16de02f5005fd544a0183112bcc22412", size = 31309, upload-time = "2026-02-04T21:51:09.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:dff25a9a6435f527d7c65608a7e62783fc12076e7d44487a4911ee91be5a8ac8", size = 2114430, upload-time = "2026-02-04T21:54:19.485Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1e/8a54166a8c5e4f5cb516514bdf4090be4d51a71e8d9f6d98c0aa00fe45d4/pywinpty-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:fbc1e230e5b193eef4431cba3f39996a288f9958f9c9f092c8a961d930ee8f68", size = 236191, upload-time = "2026-02-04T21:50:36.239Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c9081df0e49ffa86d15db4a6ba61530630e48707f987df42c9d3313537e81fc0", size = 2113098, upload-time = "2026-02-04T21:54:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/b9/53/7278223c493ccfe4883239cf06c823c56460a8010e0fc778eef67858dc14/pywinpty-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:15e79d870e18b678fb8a5a6105fd38496b55697c66e6fc0378236026bc4d59e9", size = 234901, upload-time = "2026-02-04T21:53:31.35Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -1984,176 +574,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] -[[package]] -name = "pyzmq" -version = "27.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, -] - -[[package]] -name = "readchar" -version = "4.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/49/a10341024c45bed95d13197ec9ef0f4e2fd10b5ca6e7f8d7684d18082398/readchar-4.2.2.tar.gz", hash = "sha256:e3b270fe16fc90c50ac79107700330a133dd4c63d22939f5b03b4f24564d5dd8", size = 9762, upload-time = "2026-04-06T19:45:54.226Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/ca/36133653e00939922dd1416f4c56177361289172a30563fcb9552c9ccde4/readchar-4.2.2-py3-none-any.whl", hash = "sha256:92daf7e42c52b0787e6c75d01ecfb9a94f4ceff3764958b570c1dddedd47b200", size = 9401, upload-time = "2026-04-06T19:45:52.993Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - -[[package]] -name = "requests" -version = "2.33.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, -] - -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, -] - -[[package]] -name = "rfc3986-validator" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, -] - -[[package]] -name = "rfc3987-syntax" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lark" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, -] - -[[package]] -name = "rich" -version = "14.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, -] - -[[package]] -name = "rpds-py" -version = "0.30.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, -] - [[package]] name = "ruff" version = "0.15.10" @@ -2179,40 +599,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, ] -[[package]] -name = "rustyms" -version = "0.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/40/37bdfb9c6591840b817e758cf7c4542c2f44071cc3094edfba9735245a75/rustyms-0.8.3.tar.gz", hash = "sha256:e0daa6e96f42b255168505ca99116cd11ae5cab2cd17eca0df84564836ac6f9b", size = 7751111, upload-time = "2024-03-18T16:40:31.433Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/4f987ab286294b6b08f1c250fdc551f1c080d7487966ac8451448a61e50b/rustyms-0.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d72f84a2e7ba752401d5c0cd6b556a2952cace9a4e2c2c13fbf6f8a781dd8cb4", size = 4173710, upload-time = "2024-03-18T16:39:07.948Z" }, - { url = "https://files.pythonhosted.org/packages/b4/af/321bbd8f75b46ad5f598683f22fe317ac631131666dfc220c72052bfd370/rustyms-0.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:13f7b7c38dd4427e6cb4796ecd7555cc0cbe4452e445ef041039598cbe5f8ee9", size = 4280101, upload-time = "2024-03-18T16:39:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e7/be3f9df9e38cf101bba0de68bec8626c139f927e2c342a2fffb351c25514/rustyms-0.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e6f2dd53df1f62349c7a4ee6a2542a772c03547538c0ba5a1aae7e3d03b97a2", size = 8758490, upload-time = "2024-03-18T16:39:11.624Z" }, - { url = "https://files.pythonhosted.org/packages/06/9c/bb0ec7c3b2743cfbbd72f9398b7fbe9039fe3f740da976216c40bec8c0a2/rustyms-0.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad6f65b1ea61db60fafeacffc11dc31bc0dab5e91243ccfc7351f7472bf7e59", size = 8997876, upload-time = "2024-03-18T16:39:14.504Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7a/845814ff1a070735ac8fabc885b1918a35d85d36f2c7a506b836251ca6ce/rustyms-0.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99edce59c95d7429833ea3d7408406ebae8fea057d1f933458db9250d9fb199e", size = 8610256, upload-time = "2024-03-18T16:39:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/be/6a/e034dded108d4367484cb7444876a4643c79512bb2917e04738c0c17f67b/rustyms-0.8.3-cp311-none-win32.whl", hash = "sha256:8e45b05af1ab4a91c68a090e79297fdaba95062d4a609a19fa26407540f8629e", size = 3898428, upload-time = "2024-03-18T16:39:20.43Z" }, - { url = "https://files.pythonhosted.org/packages/ae/cf/f9262680f1c1552bde6ebd8b6f0a2b726f51bd55ac227d79ba804ce7bc98/rustyms-0.8.3-cp311-none-win_amd64.whl", hash = "sha256:6072cddf987d06ae0b764700e82fa58804b64d3269249b2e82c0d99e0259d097", size = 3994738, upload-time = "2024-03-18T16:39:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cb/e1e903bbb5f2eae9cacd0d3c2966e5cd1f7a6b485f11fe0725c9bf86b029/rustyms-0.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c69cfe2bce10b58eb233324392b6e322f2fd2bf2a52ac9c253f3f6132a1823a", size = 4187488, upload-time = "2024-03-18T16:39:25.063Z" }, - { url = "https://files.pythonhosted.org/packages/f5/1c/1757dd6788ecc6cd80b0da1c3157c5282e13b3b2ee56bcbd83c6b276b321/rustyms-0.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8860bb36f9f20d620de7354a261a913c30a870c70170f0d4230763fc26da053c", size = 4292154, upload-time = "2024-03-18T16:39:26.846Z" }, - { url = "https://files.pythonhosted.org/packages/52/df/fd6c25c63d905027c350b7525c7d98fac3bfa712f93d0ed40870a758ffb8/rustyms-0.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bca139c56c663d6a60fca31498571812ec096f751a92c3b622ac64301de7f8e", size = 8843655, upload-time = "2024-03-18T16:39:29.053Z" }, - { url = "https://files.pythonhosted.org/packages/49/02/08a38d2179dc7eaf004de51465171cb398f8b2e368fe87d5b7abe6103cda/rustyms-0.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d131e8c0313dfa6e91dd2e0293f38dba317f76b4027d0689ab73fc90ae953f16", size = 9074763, upload-time = "2024-03-18T16:39:31.979Z" }, - { url = "https://files.pythonhosted.org/packages/ba/94/58ce847960dbc4ba9d95b94d28bf0b3e9fcf2007634f58968c50da36884c/rustyms-0.8.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7bf034aa0ae86a8c5b67ef1d2e3d33ae53bba1a72738d4934c95d9681af81526", size = 8676588, upload-time = "2024-03-18T16:39:34.994Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9c/4db326f2594f6285d070a529ac58b9ec1277c90f5f7b49848288b44a97fa/rustyms-0.8.3-cp312-none-win32.whl", hash = "sha256:4b840c56de30e5d3326d82ae5a50f0b398e1b03a2b6f202d2b1545aeee3bf7f4", size = 3915358, upload-time = "2024-03-18T16:39:37.731Z" }, - { url = "https://files.pythonhosted.org/packages/2f/95/09fc7d407d779b1253059dfcfd8bfcf32bc22c39f4719fd6f830590aab60/rustyms-0.8.3-cp312-none-win_amd64.whl", hash = "sha256:e2b015dafa873f3550ef591c413dbc9a3e9f6442f0ebebf1fd812441568310fc", size = 4014280, upload-time = "2024-03-18T16:39:39.449Z" }, -] - -[[package]] -name = "s3transfer" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "botocore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, -] - [[package]] name = "scipy" version = "1.17.1" @@ -2258,24 +644,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, ] -[[package]] -name = "send2trash" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, -] - -[[package]] -name = "setuptools" -version = "82.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -2285,75 +653,6 @@ 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 = "soupsieve" -version = "2.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, -] - -[[package]] -name = "speclib-builder" -version = "0.27.0" -source = { editable = "python/speclib_builder" } -dependencies = [ - { name = "loguru" }, - { name = "msgpack" }, - { name = "numpy" }, - { name = "polars" }, - { name = "pydantic" }, - { name = "pyteomics" }, - { name = "rich" }, - { name = "rustyms" }, - { name = "tqdm" }, - { name = "uniplot" }, - { name = "zstandard" }, -] - -[package.optional-dependencies] -ml = [ - { name = "boto3" }, - { name = "cloudpathlib", extra = ["s3"] }, - { name = "elfragmentadonnx" }, - { name = "elfragmentador-core" }, -] - -[package.metadata] -requires-dist = [ - { name = "boto3", marker = "extra == 'ml'" }, - { name = "cloudpathlib", extras = ["s3"], marker = "extra == 'ml'" }, - { name = "elfragmentadonnx", marker = "extra == 'ml'", url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentadonnx-0.23.0-py3-none-any.whl" }, - { name = "elfragmentador-core", marker = "extra == 'ml'", url = "https://github.com/TalusBio/2024_dev_talus_prospect/releases/download/v0.23.0/elfragmentador_core-0.23.0-py3-none-any.whl" }, - { name = "loguru" }, - { name = "msgpack" }, - { name = "numpy", specifier = ">=2,<3" }, - { name = "polars" }, - { name = "pydantic", specifier = ">=2.11.4,<3" }, - { name = "pyteomics" }, - { name = "rich" }, - { name = "rustyms" }, - { name = "tqdm" }, - { name = "uniplot" }, - { name = "zstandard" }, -] -provides-extras = ["ml"] - -[[package]] -name = "stack-data" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, -] - [[package]] name = "statsmodels" version = "0.14.6" @@ -2381,40 +680,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" }, ] -[[package]] -name = "sympy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, -] - -[[package]] -name = "terminado" -version = "0.18.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, -] - [[package]] name = "timsseek-workspace" -version = "0.27.0" +version = "0.28.0" source = { virtual = "." } -dependencies = [ - { name = "jupyter" }, - { name = "speclib-builder", extra = ["ml"] }, -] [package.dev-dependencies] dev = [ @@ -2426,7 +695,6 @@ dev = [ { name = "uv" }, ] interactive = [ - { name = "ipykernel" }, { name = "matplotlib" }, { name = "pandas" }, { name = "polars" }, @@ -2435,10 +703,6 @@ interactive = [ ] [package.metadata] -requires-dist = [ - { name = "jupyter", extras = ["python"], specifier = ">=1.1.1" }, - { name = "speclib-builder", extras = ["ml"], editable = "python/speclib_builder" }, -] [package.metadata.requires-dev] dev = [ @@ -2450,25 +714,12 @@ dev = [ { name = "uv" }, ] interactive = [ - { name = "ipykernel" }, { name = "matplotlib" }, { name = "pandas" }, { name = "polars" }, { name = "vizta" }, ] -[[package]] -name = "tinycss2" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, -] - [[package]] name = "toml" version = "0.10.2" @@ -2478,65 +729,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] -[[package]] -name = "tornado" -version = "6.5.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - -[[package]] -name = "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 = "2026.1" @@ -2546,37 +738,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, ] -[[package]] -name = "uniplot" -version = "0.21.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "readchar" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/66/198a3921ad6fd3c5e4de1e8d6ef4d9781d6c992e36f14bb5325717c0c8df/uniplot-0.21.5.tar.gz", hash = "sha256:24adc3c44b6719f3acd28cc8cac4afa621a0ff63f6dce3f8c8615c27923edb18", size = 35593, upload-time = "2026-01-04T12:40:29.333Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/e7/b3cd93b172b4e24f132af7e235623f68e704fd09b828822461eca0e98066/uniplot-0.21.5-py3-none-any.whl", hash = "sha256:7ba1bcef43376991fa1936c85186b08ce46065a63068ce0735df491c76e001f1", size = 37346, upload-time = "2026-01-04T12:40:28.269Z" }, -] - -[[package]] -name = "uri-template" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] - [[package]] name = "uv" version = "0.11.6" @@ -2659,99 +820,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/82/8786e7633cd2ef03c wheels = [ { url = "https://files.pythonhosted.org/packages/48/ee/b1dcfa25f18e6964cf73906c5c39a15654d0a702670ff89e9ed3ebab3e05/vizta-1.1.2-py3-none-any.whl", hash = "sha256:39d66bc7c30256d47a5cd2ca0a0924bd8dc65b5f63ea686da8216b606b95ee3c", size = 8864, upload-time = "2025-09-01T21:08:57.577Z" }, ] - -[[package]] -name = "wcwidth" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, -] - -[[package]] -name = "webcolors" -version = "25.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, -] - -[[package]] -name = "webencodings" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, -] - -[[package]] -name = "websocket-client" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, -] - -[[package]] -name = "widgetsnbextension" -version = "4.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, -] - -[[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 = "zstandard" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, - { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, - { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, - { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, - { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, - { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, -]