From 554e4e003a971035eefe4bbd706b01b77434a9e3 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 21:20:51 -0700 Subject: [PATCH 01/24] feat(timsseek): add DigestSlice::as_str() for zero-alloc access --- rust/timsseek/src/models/digest.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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(); From e5df3d7c765b4398278a9807895ebd215a3c6ebb Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 21:21:04 -0700 Subject: [PATCH 02/24] feat(timsseek): add SerSpeclibElement constructors + SpeclibWriter for msgpack.zst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add pub constructors (new()) to PrecursorEntry, ReferenceEG, and SerSpeclibElement so external crates can construct speclib entries. Make PrecursorEntry pub. Add SpeclibWriter wrapping zstd::Encoder for streaming msgpack.zst output. Re-export all four types from data_sources. Two roundtrip tests cover both rmp_serde encode/decode and the full writer→reader pipeline. --- rust/timsseek/src/data_sources/mod.rs | 4 + rust/timsseek/src/data_sources/speclib.rs | 129 +++++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) 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..62b2ad4 100644 --- a/rust/timsseek/src/data_sources/speclib.rs +++ b/rust/timsseek/src/data_sources/speclib.rs @@ -41,6 +41,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 +98,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 +182,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 @@ -765,6 +809,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::*; @@ -1215,4 +1299,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); + } } From 884bb02e8631eda7eb2b2a007f95c8c4d323205d Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 21:32:54 -0700 Subject: [PATCH 03/24] feat: scaffold speclib_build_cli crate --- Cargo.toml | 4 +++- rust/speclib_build_cli/Cargo.toml | 29 ++++++++++++++++++++++++++++ rust/speclib_build_cli/src/cli.rs | 1 + rust/speclib_build_cli/src/config.rs | 1 + rust/speclib_build_cli/src/main.rs | 6 ++++++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 rust/speclib_build_cli/Cargo.toml create mode 100644 rust/speclib_build_cli/src/cli.rs create mode 100644 rust/speclib_build_cli/src/config.rs create mode 100644 rust/speclib_build_cli/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 545df67..c42dbcd 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,7 +22,8 @@ default-members = [ "rust/timsseek_cli", "rust/timsquery", "rust/timsquery_cli", - "rust/timsquery_viewer" + "rust/timsquery_viewer", + "rust/speclib_build_cli" ] [workspace.package] diff --git a/rust/speclib_build_cli/Cargo.toml b/rust/speclib_build_cli/Cargo.toml new file mode 100644 index 0000000..7450424 --- /dev/null +++ b/rust/speclib_build_cli/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "speclib_build_cli" +version.workspace = true +edition.workspace = true +license.workspace = true + +[[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..eaab9c2 --- /dev/null +++ b/rust/speclib_build_cli/src/cli.rs @@ -0,0 +1 @@ +// CLI arg definitions — filled in Task 4 diff --git a/rust/speclib_build_cli/src/config.rs b/rust/speclib_build_cli/src/config.rs new file mode 100644 index 0000000..a8dd7f7 --- /dev/null +++ b/rust/speclib_build_cli/src/config.rs @@ -0,0 +1 @@ +// Config schema — filled in Task 4 diff --git a/rust/speclib_build_cli/src/main.rs b/rust/speclib_build_cli/src/main.rs new file mode 100644 index 0000000..8cfd083 --- /dev/null +++ b/rust/speclib_build_cli/src/main.rs @@ -0,0 +1,6 @@ +mod cli; +mod config; + +fn main() { + eprintln!("speclib_build: not yet implemented"); +} From 4a756b340ac5bbe1bb8d3608adc7b4e6965c86b7 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 21:37:46 -0700 Subject: [PATCH 04/24] feat(speclib_build): CLI, config, mods, dedup, decoys - clap CLI with all args + TOML config merge (CLI > TOML > defaults) - Proforma mod parser: fixed + variable mod application - Bloom + bucket HashMap peptide dedup - REVERSE + EDGE_MUTATE decoy strategies - Example config at repo root --- Cargo.lock | 245 ++++++++++++++++- example_speclib_config.toml | 78 ++++++ rust/speclib_build_cli/src/cli.rs | 113 +++++++- rust/speclib_build_cli/src/config.rs | 398 ++++++++++++++++++++++++++- rust/speclib_build_cli/src/decoys.rs | 147 ++++++++++ rust/speclib_build_cli/src/dedup.rs | 105 +++++++ rust/speclib_build_cli/src/main.rs | 21 +- rust/speclib_build_cli/src/mods.rs | 348 +++++++++++++++++++++++ 8 files changed, 1445 insertions(+), 10 deletions(-) create mode 100644 example_speclib_config.toml create mode 100644 rust/speclib_build_cli/src/decoys.rs create mode 100644 rust/speclib_build_cli/src/dedup.rs create mode 100644 rust/speclib_build_cli/src/mods.rs diff --git a/Cargo.lock b/Cargo.lock index 9c9c900..f659ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -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", @@ -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.27.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" @@ -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/example_speclib_config.toml b/example_speclib_config.toml new file mode 100644 index 0000000..10eef2b --- /dev/null +++ b/example_speclib_config.toml @@ -0,0 +1,78 @@ +# 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_2020_intensity_HCD" +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 + +# ── 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/rust/speclib_build_cli/src/cli.rs b/rust/speclib_build_cli/src/cli.rs index eaab9c2..8e33a8d 100644 --- a/rust/speclib_build_cli/src/cli.rs +++ b/rust/speclib_build_cli/src/cli.rs @@ -1 +1,112 @@ -// CLI arg definitions — filled in Task 4 +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, + + // ── 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 index a8dd7f7..2247bf2 100644 --- a/rust/speclib_build_cli/src/config.rs +++ b/rust/speclib_build_cli/src/config.rs @@ -1 +1,397 @@ -// Config schema — filled in Task 4 +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_2020_intensity_HCD".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, +} + +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(), + } + } +} + +#[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; + } + + // ── 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/main.rs b/rust/speclib_build_cli/src/main.rs index 8cfd083..52644e9 100644 --- a/rust/speclib_build_cli/src/main.rs +++ b/rust/speclib_build_cli/src/main.rs @@ -1,6 +1,25 @@ mod cli; mod config; +mod dedup; +mod decoys; +mod mods; + +use clap::Parser; +use cli::Cli; +use config::SpeclibBuildConfig; fn main() { - eprintln!("speclib_build: not yet implemented"); + let cli = Cli::parse(); + 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); + } + eprintln!("Config loaded. Pipeline not yet implemented."); } 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()]); + } +} From a85e8497988b54185e284e41345bcf7ae7de2a2c Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 21:43:32 -0700 Subject: [PATCH 05/24] feat(speclib_build): Koina HTTP client + model registry + request/response adapter --- rust/speclib_build_cli/src/koina/adapter.rs | 311 ++++++++++++++++++++ rust/speclib_build_cli/src/koina/mod.rs | 108 +++++++ rust/speclib_build_cli/src/koina/models.rs | 113 +++++++ rust/speclib_build_cli/src/main.rs | 1 + 4 files changed, 533 insertions(+) create mode 100644 rust/speclib_build_cli/src/koina/adapter.rs create mode 100644 rust/speclib_build_cli/src/koina/mod.rs create mode 100644 rust/speclib_build_cli/src/koina/models.rs 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..86472d2 --- /dev/null +++ b/rust/speclib_build_cli/src/koina/adapter.rs @@ -0,0 +1,311 @@ +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; + + let annotations_flat: Vec = annotations_tensor + .data + .iter() + .map(|v| v.as_str().unwrap_or("").to_string()) + .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() +} + +// ── 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..a839876 --- /dev/null +++ b/rust/speclib_build_cli/src/koina/mod.rs @@ -0,0 +1,108 @@ +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() { + return resp + .json::() + .await + .map_err(|e| format!("failed to deserialise Koina response: {e}")); + } + + // 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..b266427 --- /dev/null +++ b/rust/speclib_build_cli/src/koina/models.rs @@ -0,0 +1,113 @@ +use serde::{Deserialize, Serialize}; + +// ── Model registry ─────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FragmentModel { + Prosit2020IntensityHcd, + AlphaPeptDeepMs2Generic, +} + +impl FragmentModel { + pub fn from_name(name: &str) -> Result { + match name { + "Prosit_2020_intensity_HCD" => Ok(Self::Prosit2020IntensityHcd), + "AlphaPeptDeep_ms2_generic" => Ok(Self::AlphaPeptDeepMs2Generic), + other => Err(format!( + "unknown fragment model {other:?}; valid: Prosit_2020_intensity_HCD, AlphaPeptDeep_ms2_generic" + )), + } + } + + pub fn model_name(&self) -> &str { + match self { + Self::Prosit2020IntensityHcd => "Prosit_2020_intensity_HCD", + 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/main.rs b/rust/speclib_build_cli/src/main.rs index 52644e9..1b3b8ab 100644 --- a/rust/speclib_build_cli/src/main.rs +++ b/rust/speclib_build_cli/src/main.rs @@ -2,6 +2,7 @@ mod cli; mod config; mod dedup; mod decoys; +mod koina; mod mods; use clap::Parser; From 84d76fdfa855cff66fbafb2b7ff308e85a922da2 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:01:37 -0700 Subject: [PATCH 06/24] =?UTF-8?q?feat(speclib=5Fbuild):=20entry=20builder?= =?UTF-8?q?=20=E2=80=94=20Koina=20predictions=20to=20SerSpeclibElement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/speclib_build_cli/src/entry.rs | 297 ++++++++++++++++++++++++++++ rust/speclib_build_cli/src/main.rs | 1 + 2 files changed, 298 insertions(+) create mode 100644 rust/speclib_build_cli/src/entry.rs diff --git a/rust/speclib_build_cli/src/entry.rs b/rust/speclib_build_cli/src/entry.rs new file mode 100644 index 0000000..817e9f4 --- /dev/null +++ b/rust/speclib_build_cli/src/entry.rs @@ -0,0 +1,297 @@ +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" +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 +} + +/// Compute the monoisotopic precursor m/z using rustyms. +fn compute_precursor_mz(stripped_seq: &str, charge: u8) -> Option { + use rustyms::prelude::*; + let peptide = Peptidoform::pro_forma(stripped_seq, 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) +} + +// ── 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. Strip modifications for formula-based computations. + let stripped = strip_mods(sequence); + + // 2. Carbon / sulphur count → isotope distribution. + let (ncarbon, nsulphur) = count_carbon_sulphur_in_sequence(&stripped).ok()?; + let iso = peptide_isotopes(ncarbon, nsulphur); + + // 3. Precursor m/z. + let precursor_mz = compute_precursor_mz(&stripped, 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_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/main.rs b/rust/speclib_build_cli/src/main.rs index 1b3b8ab..667c340 100644 --- a/rust/speclib_build_cli/src/main.rs +++ b/rust/speclib_build_cli/src/main.rs @@ -2,6 +2,7 @@ mod cli; mod config; mod dedup; mod decoys; +mod entry; mod koina; mod mods; From bd2e7aa3ac450e061ce94d661704c4b1bc7610d6 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:07:06 -0700 Subject: [PATCH 07/24] =?UTF-8?q?feat(speclib=5Fbuild):=20pipeline=20orche?= =?UTF-8?q?stration=20=E2=80=94=20digest=E2=86=92dedup=E2=86=92expand?= =?UTF-8?q?=E2=86=92predict=E2=86=92write?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/speclib_build_cli/src/main.rs | 17 +- rust/speclib_build_cli/src/pipeline.rs | 277 +++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 rust/speclib_build_cli/src/pipeline.rs diff --git a/rust/speclib_build_cli/src/main.rs b/rust/speclib_build_cli/src/main.rs index 667c340..c088537 100644 --- a/rust/speclib_build_cli/src/main.rs +++ b/rust/speclib_build_cli/src/main.rs @@ -5,6 +5,7 @@ mod decoys; mod entry; mod koina; mod mods; +mod pipeline; use clap::Parser; use cli::Cli; @@ -12,6 +13,15 @@ use 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) => { @@ -23,5 +33,10 @@ fn main() { eprintln!("Invalid config: {e}"); std::process::exit(1); } - eprintln!("Config loaded. Pipeline not yet implemented."); + + let rt = tokio::runtime::Runtime::new().unwrap(); + if let Err(e) = rt.block_on(pipeline::run(&config)) { + eprintln!("Pipeline error: {e}"); + std::process::exit(1); + } } diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs new file mode 100644 index 0000000..55e2789 --- /dev/null +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -0,0 +1,277 @@ +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(()); + } + + let inputs: Vec = batch + .iter() + .map(|item| PredictionInput { + sequence: item.sequence.clone(), + 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, + }; + + 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); + 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); + 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 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?; + } + } + } + + 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(()) +} From 294c8bb2667148300b8cc4cd12660a68552353da Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:29:47 -0700 Subject: [PATCH 08/24] feat(speclib_build): integration tests, lib.rs, Taskfile recipes - lib.rs re-exports for integration test access - 3 integration tests: digestion+dedup, mod chain, decoy roundtrip - Taskfile: speclib:build, speclib:local-koina, speclib:stop-koina --- Taskfile.yml | 22 ++++++ rust/speclib_build_cli/Cargo.toml | 4 ++ rust/speclib_build_cli/src/lib.rs | 8 +++ rust/speclib_build_cli/src/main.rs | 15 +--- rust/speclib_build_cli/tests/integration.rs | 70 +++++++++++++++++++ .../tests/test_data/tiny.fasta | 4 ++ 6 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 rust/speclib_build_cli/src/lib.rs create mode 100644 rust/speclib_build_cli/tests/integration.rs create mode 100644 rust/speclib_build_cli/tests/test_data/tiny.fasta diff --git a/Taskfile.yml b/Taskfile.yml index 5fd9268..fdc0cb8 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -48,3 +48,25 @@ 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) + cmds: + - | + docker run --rm -d \ + --name koina-local \ + --shm-size 8G \ + -p 8500:8500 -p 8501:8501 -p 8502:8502 \ + ghcr.io/wilhelm-lab/koina:latest + - echo "Koina running at http://localhost:8501/v2/models" + - echo "Use --koina-url http://localhost:8501/v2/models" + + speclib:stop-koina: + desc: Stop local Koina server + cmds: + - docker stop koina-local + diff --git a/rust/speclib_build_cli/Cargo.toml b/rust/speclib_build_cli/Cargo.toml index 7450424..3d30f60 100644 --- a/rust/speclib_build_cli/Cargo.toml +++ b/rust/speclib_build_cli/Cargo.toml @@ -4,6 +4,10 @@ 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" 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 index c088537..7cadceb 100644 --- a/rust/speclib_build_cli/src/main.rs +++ b/rust/speclib_build_cli/src/main.rs @@ -1,15 +1,6 @@ -mod cli; -mod config; -mod dedup; -mod decoys; -mod entry; -mod koina; -mod mods; -mod pipeline; - use clap::Parser; -use cli::Cli; -use config::SpeclibBuildConfig; +use speclib_build_cli::cli::Cli; +use speclib_build_cli::config::SpeclibBuildConfig; fn main() { let cli = Cli::parse(); @@ -35,7 +26,7 @@ fn main() { } let rt = tokio::runtime::Runtime::new().unwrap(); - if let Err(e) = rt.block_on(pipeline::run(&config)) { + 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/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 From 0a57a1a9d1d256eae0612b5851a35d8a0479979e Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:43:17 -0700 Subject: [PATCH 09/24] fix(speclib_build): strip mods for Koina, convert annotation format - Prosit expects bare AA sequences, strip bracket mods before sending - Convert Koina annotation format (y1+1) to mzPAF format (y1^1) - Make strip_mods pub for pipeline use --- rust/speclib_build_cli/src/entry.rs | 2 +- rust/speclib_build_cli/src/koina/adapter.rs | 22 ++++++++++++++++++++- rust/speclib_build_cli/src/pipeline.rs | 5 +++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/rust/speclib_build_cli/src/entry.rs b/rust/speclib_build_cli/src/entry.rs index 817e9f4..1724060 100644 --- a/rust/speclib_build_cli/src/entry.rs +++ b/rust/speclib_build_cli/src/entry.rs @@ -23,7 +23,7 @@ pub struct EntryFilters { /// Strip bracket-enclosed modifications from a sequence. /// /// "PEPTC[U:4]IDEK" → "PEPTCIDEK" -fn strip_mods(seq: &str) -> String { +pub fn strip_mods(seq: &str) -> String { let mut out = String::with_capacity(seq.len()); let mut depth = 0usize; for ch in seq.chars() { diff --git a/rust/speclib_build_cli/src/koina/adapter.rs b/rust/speclib_build_cli/src/koina/adapter.rs index 86472d2..9435fac 100644 --- a/rust/speclib_build_cli/src/koina/adapter.rs +++ b/rust/speclib_build_cli/src/koina/adapter.rs @@ -78,10 +78,15 @@ pub fn parse_fragment_response( // 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| v.as_str().unwrap_or("").to_string()) + .map(|v| { + let s = v.as_str().unwrap_or(""); + koina_annotation_to_mzpaf(s) + }) .collect(); let mzs_flat: Vec = mz_tensor .data @@ -166,6 +171,21 @@ 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)] diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs index 55e2789..325cc26 100644 --- a/rust/speclib_build_cli/src/pipeline.rs +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -9,7 +9,7 @@ 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::entry::{EntryFilters, build_entry, strip_mods}; use crate::koina::KoinaClient; use crate::koina::models::{FragmentModel, PredictionInput, RtModel}; use crate::mods::{Modification, apply_fixed_mods, expand_variable_mods}; @@ -38,10 +38,11 @@ async fn flush_batch( return Ok(()); } + // Koina (Prosit) expects bare AA sequences — strip bracket mods let inputs: Vec = batch .iter() .map(|item| PredictionInput { - sequence: item.sequence.clone(), + sequence: strip_mods(&item.sequence), charge: item.charge, nce, }) From 69e070e85c86e148be0985b6f426f798e9de2acb Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:45:35 -0700 Subject: [PATCH 10/24] chore: update speclib build references from Python CLI to Rust CLI --- README.md | 16 +++++++--------- bench/wandb_bench.py | 19 ++++++++++--------- run.bash | 11 +++++------ 3 files changed, 22 insertions(+), 24 deletions(-) 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/bench/wandb_bench.py b/bench/wandb_bench.py index 14f46a6..5f63d77 100644 --- a/bench/wandb_bench.py +++ b/bench/wandb_bench.py @@ -37,19 +37,20 @@ 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", ] res = subprocess.run(args, check=True) return res 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 -- \ From 8f5be8b3eae452bf042c361a51ba0e46979ce2e1 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 22:52:39 -0700 Subject: [PATCH 11/24] feat(speclib_build): add --request-delay-ms for Koina rate limiting --- example_speclib_config.toml | 4 ++++ rust/speclib_build_cli/src/cli.rs | 4 ++++ rust/speclib_build_cli/src/config.rs | 6 ++++++ rust/speclib_build_cli/src/pipeline.rs | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/example_speclib_config.toml b/example_speclib_config.toml index 10eef2b..3e628ae 100644 --- a/example_speclib_config.toml +++ b/example_speclib_config.toml @@ -61,6 +61,10 @@ 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). diff --git a/rust/speclib_build_cli/src/cli.rs b/rust/speclib_build_cli/src/cli.rs index 8e33a8d..336a5d6 100644 --- a/rust/speclib_build_cli/src/cli.rs +++ b/rust/speclib_build_cli/src/cli.rs @@ -85,6 +85,10 @@ pub struct Cli { #[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)] diff --git a/rust/speclib_build_cli/src/config.rs b/rust/speclib_build_cli/src/config.rs index 2247bf2..c07242d 100644 --- a/rust/speclib_build_cli/src/config.rs +++ b/rust/speclib_build_cli/src/config.rs @@ -150,6 +150,8 @@ pub struct PredictionConfig { 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 { @@ -160,6 +162,7 @@ impl Default for PredictionConfig { koina_url: default_koina_url(), batch_size: default_batch_size(), nce: default_nce(), + request_delay_ms: 0, } } } @@ -300,6 +303,9 @@ impl SpeclibBuildConfig { 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 { diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs index 325cc26..42f8b46 100644 --- a/rust/speclib_build_cli/src/pipeline.rs +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -172,6 +172,11 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box 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; @@ -246,6 +251,9 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box Date: Wed, 15 Apr 2026 23:00:47 -0700 Subject: [PATCH 12/24] fix(speclib_build): better Koina response error reporting with body preview --- rust/speclib_build_cli/src/koina/mod.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rust/speclib_build_cli/src/koina/mod.rs b/rust/speclib_build_cli/src/koina/mod.rs index a839876..c8b2876 100644 --- a/rust/speclib_build_cli/src/koina/mod.rs +++ b/rust/speclib_build_cli/src/koina/mod.rs @@ -91,10 +91,16 @@ impl KoinaClient { } if status.is_success() { - return resp - .json::() - .await - .map_err(|e| format!("failed to deserialise Koina response: {e}")); + 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. From cce0da62cd01b85861bb4e899f4df3e89fbabb46 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:20:40 -0700 Subject: [PATCH 13/24] fix(speclib_build): skip peptides with non-standard AAs (U/B/J/Z/X), warn affected proteins --- rust/speclib_build_cli/src/pipeline.rs | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs index 42f8b46..723e724 100644 --- a/rust/speclib_build_cli/src/pipeline.rs +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -96,6 +96,16 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box> = collection .sequences .iter() @@ -106,6 +116,10 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box 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 { @@ -125,6 +139,10 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box 0 { + tracing::warn!("Skipped {skipped} peptides containing non-standard amino acids"); + } tracing::info!("Deduplicated to {} unique peptides", deduped.len()); deduped } else { @@ -284,3 +302,17 @@ pub async fn run(config: &SpeclibBuildConfig) -> Result<(), Box) -> (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) +} From 0d455e0bf35951ba618322e85b9fe269e74d28c2 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:22:26 -0700 Subject: [PATCH 14/24] feat(bench): add --koina-url flag for local/custom Koina server --- bench/wandb_bench.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bench/wandb_bench.py b/bench/wandb_bench.py index 5f63d77..f2fdf3e 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(): @@ -52,6 +53,8 @@ def build_speclib(self): "-o", str(self.speclib_location), ] + if self.koina_url: + args.extend(["--koina-url", self.koina_url]) res = subprocess.run(args, check=True) return res @@ -215,7 +218,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" @@ -233,6 +236,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) @@ -245,6 +249,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 @@ -259,4 +269,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) From 83d18718b6bdb8b6e75afd763c087161276d73e2 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:27:06 -0700 Subject: [PATCH 15/24] fix(taskfile): reuse koina container to avoid re-downloading models --- Taskfile.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index fdc0cb8..ba7a0b7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -54,16 +54,23 @@ tasks: - cargo run --release -p speclib_build_cli -- {{.CLI_ARGS}} speclib:local-koina: - desc: Start local Koina server (CPU, Docker) + desc: Start local Koina server (CPU, Docker). First run downloads all models (~10-30 min). cmds: - | - docker run --rm -d \ - --name koina-local \ - --shm-size 8G \ - -p 8500:8500 -p 8501:8501 -p 8502:8502 \ - ghcr.io/wilhelm-lab/koina:latest + 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 From efb7735beabfbe79b08cca7ab3b7e4a70969eb1a Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:29:40 -0700 Subject: [PATCH 16/24] fix(bench): add request delay for public Koina rate limiting --- bench/wandb_bench.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bench/wandb_bench.py b/bench/wandb_bench.py index f2fdf3e..fa43a91 100644 --- a/bench/wandb_bench.py +++ b/bench/wandb_bench.py @@ -55,6 +55,9 @@ def build_speclib(self): ] 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 From 4318c94eea5175a2776a96d7feae62ccbbb9849a Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:49:36 -0700 Subject: [PATCH 17/24] fix(speclib_build): use modified sequence for precursor mz + Koina input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Precursor m/z now computed from modified sequence (includes +57 for Cys carbam) - Koina receives modified sequences with [UNIMOD:N] notation (Prosit handles them) - to_proforma converts [U:N] → [UNIMOD:N] (rustyms parses [U:N] as element Uranium) - Isotope distribution also uses modified formula --- rust/speclib_build_cli/src/entry.rs | 44 ++++++++++++++++++++------ rust/speclib_build_cli/src/pipeline.rs | 6 ++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/rust/speclib_build_cli/src/entry.rs b/rust/speclib_build_cli/src/entry.rs index 1724060..388061d 100644 --- a/rust/speclib_build_cli/src/entry.rs +++ b/rust/speclib_build_cli/src/entry.rs @@ -39,10 +39,19 @@ pub fn strip_mods(seq: &str) -> String { 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. -fn compute_precursor_mz(stripped_seq: &str, charge: u8) -> Option { +/// Input should be the modified sequence (mods included in mass). +fn compute_precursor_mz(modified_seq: &str, charge: u8) -> Option { use rustyms::prelude::*; - let peptide = Peptidoform::pro_forma(stripped_seq, None).ok()?; + 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() { @@ -53,6 +62,12 @@ fn compute_precursor_mz(stripped_seq: &str, charge: u8) -> Option { 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`]. @@ -71,15 +86,12 @@ pub fn build_entry( rt: &RtPrediction, filters: &EntryFilters, ) -> Option { - // 1. Strip modifications for formula-based computations. - let stripped = strip_mods(sequence); - - // 2. Carbon / sulphur count → isotope distribution. - let (ncarbon, nsulphur) = count_carbon_sulphur_in_sequence(&stripped).ok()?; + // 1. Carbon / sulphur count from modified sequence (includes mod contributions). + let (ncarbon, nsulphur) = count_cs_modified(sequence)?; let iso = peptide_isotopes(ncarbon, nsulphur); - // 3. Precursor m/z. - let precursor_mz = compute_precursor_mz(&stripped, charge)?; + // 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 { @@ -200,6 +212,20 @@ mod tests { 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(); diff --git a/rust/speclib_build_cli/src/pipeline.rs b/rust/speclib_build_cli/src/pipeline.rs index 723e724..ac4d1c6 100644 --- a/rust/speclib_build_cli/src/pipeline.rs +++ b/rust/speclib_build_cli/src/pipeline.rs @@ -9,7 +9,7 @@ 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, strip_mods}; +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}; @@ -38,11 +38,11 @@ async fn flush_batch( return Ok(()); } - // Koina (Prosit) expects bare AA sequences — strip bracket mods + // Koina/Prosit accepts UNIMOD notation — convert our short form [U:N] → [UNIMOD:N] let inputs: Vec = batch .iter() .map(|item| PredictionInput { - sequence: strip_mods(&item.sequence), + sequence: item.sequence.replace("[U:", "[UNIMOD:"), charge: item.charge, nce, }) From 558002c18f143c819829fb24788e17fd6696f95c Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Wed, 15 Apr 2026 23:52:46 -0700 Subject: [PATCH 18/24] feat(speclib_build): add Prosit_2023_intensity_timsTOF, make it default - Add Prosit_2023_intensity_timsTOF and Prosit_2020_intensity_CID to model registry - Default fragment model now timsTOF-specific (was HCD) - Same Triton v2 schema, drop-in compatible --- example_speclib_config.toml | 2 +- rust/speclib_build_cli/src/config.rs | 2 +- rust/speclib_build_cli/src/koina/models.rs | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/example_speclib_config.toml b/example_speclib_config.toml index 3e628ae..717afdc 100644 --- a/example_speclib_config.toml +++ b/example_speclib_config.toml @@ -49,7 +49,7 @@ strategy = "none" # ── Prediction ──────────────────────────────────────────────────────────────── [prediction] # Koina model names for fragment intensities and retention time. -fragment_model = "Prosit_2020_intensity_HCD" +fragment_model = "Prosit_2023_intensity_timsTOF" rt_model = "Prosit_2019_irt" # Base URL for the Koina inference service (no trailing slash). diff --git a/rust/speclib_build_cli/src/config.rs b/rust/speclib_build_cli/src/config.rs index c07242d..383a8de 100644 --- a/rust/speclib_build_cli/src/config.rs +++ b/rust/speclib_build_cli/src/config.rs @@ -30,7 +30,7 @@ fn default_decoy_strategy() -> String { "none".to_string() } fn default_fragment_model() -> String { - "Prosit_2020_intensity_HCD".to_string() + "Prosit_2023_intensity_timsTOF".to_string() } fn default_rt_model() -> String { "Prosit_2019_irt".to_string() diff --git a/rust/speclib_build_cli/src/koina/models.rs b/rust/speclib_build_cli/src/koina/models.rs index b266427..6b78282 100644 --- a/rust/speclib_build_cli/src/koina/models.rs +++ b/rust/speclib_build_cli/src/koina/models.rs @@ -4,24 +4,30 @@ use serde::{Deserialize, Serialize}; #[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_2020_intensity_HCD, AlphaPeptDeep_ms2_generic" + "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", } } From b72fdc8198d8799f707e141544623d756f3c775f Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 00:05:58 -0700 Subject: [PATCH 19/24] fix: default RT tolerance to Unrestricted for iRT library compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tolerance::default() now uses RtTolerance::Unrestricted (was Minutes(5,5)) - Prescore extracts full RT range, calibration maps library→observed RT - Bench config drops explicit RT tolerance (defaults to unrestricted) - Users with calibrated libraries can still set "rt": {"minutes": [N, N]} in config --- bench/wandb_bench.py | 1 - rust/timsquery/src/models/tolerance.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bench/wandb_bench.py b/bench/wandb_bench.py index fa43a91..78caf5f 100644 --- a/bench/wandb_bench.py +++ b/bench/wandb_bench.py @@ -177,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]}, }, } } 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)), } From f5b71c7321ea8c895354da9d163df7d563c1ea9e Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 14:01:08 -0700 Subject: [PATCH 20/24] feat(speclib): add Skyline transition list CSV parser New skyline_io module parses Skyline Peptide Transition List exports as a spectral library source, mirroring the DIA-NN/Spectronaut pattern (sniff + group-by-precursor + emit TimsElutionGroup plus SkylinePrecursorExtras). Wires through timsquery's dispatch, timsquery_viewer's extras view, and timsseek's speclib converter (reusing convert_diann_to_query_item via a small extras adapter). Notable behaviors: - Precursor-isotope rows (Fragment Ion Type == "precursor") are skipped; downstream computes the envelope from the sequence. - `#N/A` Library Intensity values default to 1.0. - RT/IM columns are absent in Skyline exports; defaults to 0.0 with a loud warning (use Unrestricted RT tolerance). - Modifications in `[...]` are stripped for the stripped_peptide field; modified_peptide is preserved verbatim. --- rust/timsquery/src/serde/library_file.rs | 37 ++ rust/timsquery/src/serde/mod.rs | 2 + rust/timsquery/src/serde/skyline_io.rs | 490 ++++++++++++++++++ .../sample_transition_list.csv | 100 ++++ rust/timsquery_viewer/src/file_loader.rs | 21 + rust/timsseek/src/data_sources/speclib.rs | 86 ++- 6 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 rust/timsquery/src/serde/skyline_io.rs create mode 100755 rust/timsquery/tests/skyline_io_files/sample_transition_list.csv 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/speclib.rs b/rust/timsseek/src/data_sources/speclib.rs index 62b2ad4..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, }; @@ -339,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, @@ -373,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" @@ -382,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(), }) } @@ -995,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 From cecc8b1514505866023cff384523736c3c4225df Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 14:09:23 -0700 Subject: [PATCH 21/24] chore: remove Python speclib_builder, superseded by Rust speclib_build_cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the Python speclib_builder package and all references (root pyproject workspace/deps, hatch packages, uv sources, uv.lock). The Rust speclib_build_cli now owns the digest → dedup → expand → Koina predict → write pipeline end-to-end. --- pyproject.toml | 14 - python/speclib_builder/.gitignore | 3 - python/speclib_builder/pyproject.toml | 41 -- .../speclib_builder/__init__.py | 0 .../speclib_builder/__main__.py | 4 - .../speclib_builder/speclib_builder/base.py | 160 ----- .../speclib_builder/builder.py | 158 ----- .../speclib_builder/convert.py | 308 --------- .../speclib_builder/speclib_builder/decoys.py | 106 ---- .../speclib_builder/speclib_builder/fasta.py | 81 --- .../speclib_builder/fasta_cli.py | 163 ----- .../speclib_builder/isotopes.py | 111 ---- .../speclib_builder/ml/__init__.py | 0 .../speclib_builder/ml/adam.py | 57 -- .../speclib_builder/speclib_builder/ml/ims.py | 33 - .../speclib_builder/ml/linear_regression.py | 85 --- .../speclib_builder/ml/loess.py | 61 -- .../speclib_builder/ml/ohe_peptide_embed.py | 40 -- .../speclib_builder/ml/simplertmodel.py | 119 ---- .../speclib_builder/onxx_predictor.py | 107 ---- .../speclib_builder/speclib_builder/writer.py | 144 ----- python/speclib_builder/tests/test_decoys.py | 40 -- .../tests/test_entry_matches_rust.py | 142 ----- python/speclib_builder/tests/test_isotopes.py | 17 - .../tests/test_peptide_swap.py | 272 -------- uv.lock | 598 +----------------- 26 files changed, 1 insertion(+), 2863 deletions(-) delete mode 100644 python/speclib_builder/.gitignore delete mode 100644 python/speclib_builder/pyproject.toml delete mode 100644 python/speclib_builder/speclib_builder/__init__.py delete mode 100644 python/speclib_builder/speclib_builder/__main__.py delete mode 100644 python/speclib_builder/speclib_builder/base.py delete mode 100644 python/speclib_builder/speclib_builder/builder.py delete mode 100644 python/speclib_builder/speclib_builder/convert.py delete mode 100644 python/speclib_builder/speclib_builder/decoys.py delete mode 100644 python/speclib_builder/speclib_builder/fasta.py delete mode 100644 python/speclib_builder/speclib_builder/fasta_cli.py delete mode 100644 python/speclib_builder/speclib_builder/isotopes.py delete mode 100644 python/speclib_builder/speclib_builder/ml/__init__.py delete mode 100644 python/speclib_builder/speclib_builder/ml/adam.py delete mode 100644 python/speclib_builder/speclib_builder/ml/ims.py delete mode 100644 python/speclib_builder/speclib_builder/ml/linear_regression.py delete mode 100644 python/speclib_builder/speclib_builder/ml/loess.py delete mode 100644 python/speclib_builder/speclib_builder/ml/ohe_peptide_embed.py delete mode 100644 python/speclib_builder/speclib_builder/ml/simplertmodel.py delete mode 100644 python/speclib_builder/speclib_builder/onxx_predictor.py delete mode 100644 python/speclib_builder/speclib_builder/writer.py delete mode 100644 python/speclib_builder/tests/test_decoys.py delete mode 100644 python/speclib_builder/tests/test_entry_matches_rust.py delete mode 100644 python/speclib_builder/tests/test_isotopes.py delete mode 100644 python/speclib_builder/tests/test_peptide_swap.py diff --git a/pyproject.toml b/pyproject.toml index 8eb46a6..e7078a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,6 @@ version = "0.27.0" requires-python = ">=3.11,<3.13" dependencies = [ "jupyter[python]>=1.1.1", - "speclib_builder[ml]", ] [dependency-groups] @@ -24,14 +23,6 @@ interactive = [ "vizta", ] -[tool.uv.sources] -speclib_builder = { workspace = true } - -[tool.uv.workspace] -members = [ - "python/speclib_builder", -] - [tool.ruff] target-version = "py312" @@ -42,11 +33,6 @@ 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" version_pattern = "MAJOR.MINOR.PATCH[-PYTAGNUM]" 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/uv.lock b/uv.lock index 9b0cff1..cdf605d 100644 --- a/uv.lock +++ b/uv.lock @@ -16,21 +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" @@ -165,34 +150,6 @@ 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" @@ -315,20 +272,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" @@ -438,54 +381,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" @@ -513,14 +408,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" @@ -768,15 +655,6 @@ 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" @@ -1100,31 +978,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" @@ -1203,15 +1056,6 @@ 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" @@ -1236,62 +1080,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" @@ -1430,89 +1218,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" @@ -1739,21 +1444,6 @@ 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" @@ -1797,76 +1487,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/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" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1885,16 +1505,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" @@ -2020,15 +1630,6 @@ wheels = [ { 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" @@ -2091,19 +1692,6 @@ 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" @@ -2179,40 +1767,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" @@ -2294,52 +1848,6 @@ 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" @@ -2381,18 +1889,6 @@ 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" @@ -2413,7 +1909,6 @@ version = "0.27.0" source = { virtual = "." } dependencies = [ { name = "jupyter" }, - { name = "speclib-builder", extra = ["ml"] }, ] [package.dev-dependencies] @@ -2435,10 +1930,7 @@ interactive = [ ] [package.metadata] -requires-dist = [ - { name = "jupyter", extras = ["python"], specifier = ">=1.1.1" }, - { name = "speclib-builder", extras = ["ml"], editable = "python/speclib_builder" }, -] +requires-dist = [{ name = "jupyter", extras = ["python"], specifier = ">=1.1.1" }] [package.metadata.requires-dev] dev = [ @@ -2495,18 +1987,6 @@ wheels = [ { 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" @@ -2525,18 +2005,6 @@ 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,19 +2014,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" @@ -2704,54 +2159,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b 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" }, -] From 5de6e5587ed5d05b5ee012cd4f2ce42310aa2f26 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 14:20:47 -0700 Subject: [PATCH 22/24] chore: drop jupyter and ipykernel from workspace No notebooks in the repo and no other references to jupyter. Prunes ~100 transitive deps from uv.lock. --- pyproject.toml | 5 +- uv.lock | 1345 +----------------------------------------------- 2 files changed, 4 insertions(+), 1346 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e7078a4..7235c5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,7 @@ name = "timsseek-workspace" version = "0.27.0" requires-python = ">=3.11,<3.13" -dependencies = [ - "jupyter[python]>=1.1.1", -] +dependencies = [] [dependency-groups] dev = [ @@ -19,7 +17,6 @@ interactive = [ "pandas", "polars", "matplotlib", - "ipykernel", "vizta", ] diff --git a/uv.lock b/uv.lock index cdf605d..f37d0c9 100644 --- a/uv.lock +++ b/uv.lock @@ -16,140 +16,6 @@ resolution-markers = [ "python_full_version < '3.12' and platform_machine == 's390x' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] -[[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 = "bumpver" version = "2025.1131" @@ -165,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" @@ -219,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" @@ -281,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" @@ -337,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" @@ -381,24 +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 = "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" @@ -433,52 +151,6 @@ wheels = [ { 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" @@ -488,15 +160,6 @@ 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" @@ -506,413 +169,6 @@ 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 = "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" }, -] - -[[package]] -name = "jupyterlab-server" -version = "2.28.0" -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" } -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" }, -] - -[[package]] -name = "jupyterlab-widgets" -version = "3.0.16" -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" } -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" }, -] - [[package]] name = "kiwisolver" version = "1.5.0" @@ -960,52 +216,13 @@ 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" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/0b/28a3f9abc75abbf1fa996eb2dd77e1e33a5d1aac62566e3f60a8ec8b8a22/lexid-2021.1006.tar.gz", hash = "sha256:509a3a4cc926d3dbf22b203b18a4c66c25e6473fb7c0e0d30374533ac28bafe5", size = 11525, upload-time = "2021-04-02T20:18:34.668Z" } -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 = "markupsafe" -version = "3.0.3" +version = "2021.1006" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/60/0b/28a3f9abc75abbf1fa996eb2dd77e1e33a5d1aac62566e3f60a8ec8b8a22/lexid-2021.1006.tar.gz", hash = "sha256:509a3a4cc926d3dbf22b203b18a4c66c25e6473fb7c0e0d30374533ac28bafe5", size = 11525, upload-time = "2021-04-02T20:18:34.668Z" } 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" }, + { 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]] @@ -1044,27 +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 = "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" @@ -1080,70 +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 = "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" @@ -1153,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" @@ -1218,15 +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 = "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" @@ -1265,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" @@ -1295,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" @@ -1423,70 +488,6 @@ 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 = "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" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -1546,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" @@ -1594,154 +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 = "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 = "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" @@ -1812,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" @@ -1839,29 +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 = "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" @@ -1889,27 +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 = "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" source = { virtual = "." } -dependencies = [ - { name = "jupyter" }, -] [package.dev-dependencies] dev = [ @@ -1921,7 +695,6 @@ dev = [ { name = "uv" }, ] interactive = [ - { name = "ipykernel" }, { name = "matplotlib" }, { name = "pandas" }, { name = "polars" }, @@ -1930,7 +703,6 @@ interactive = [ ] [package.metadata] -requires-dist = [{ name = "jupyter", extras = ["python"], specifier = ">=1.1.1" }] [package.metadata.requires-dev] dev = [ @@ -1942,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" @@ -1970,41 +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 = "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 = "tzdata" version = "2026.1" @@ -2014,24 +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 = "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" @@ -2114,48 +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" }, -] From 441793c428804f9aa59a91ed35b1fcce129cbdfa Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 15:28:57 -0700 Subject: [PATCH 23/24] ci(release): publish speclib_build binary --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 6ce45f3d0376b84bcb3e2a708bd9e0e092cb7a87 Mon Sep 17 00:00:00 2001 From: "J. Sebastian Paez" Date: Thu, 16 Apr 2026 15:31:54 -0700 Subject: [PATCH 24/24] chore: bump version to 0.28.0 --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- Taskfile.yml | 2 +- pyproject.toml | 4 ++-- python/timsquery_pyo3/pyproject.toml | 2 +- uv.lock | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f659ffa..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", ] @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "calibrt" -version = "0.27.0" +version = "0.28.0" dependencies = [ "array2d", "insta", @@ -3798,7 +3798,7 @@ dependencies = [ [[package]] name = "micromzpaf" -version = "0.27.0" +version = "0.28.0" dependencies = [ "rustyms", "serde", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "speclib_build_cli" -version = "0.27.0" +version = "0.28.0" dependencies = [ "bloomfilter", "clap", @@ -6216,7 +6216,7 @@ dependencies = [ [[package]] name = "timscentroid" -version = "0.27.0" +version = "0.28.0" dependencies = [ "arrow", "async-trait", @@ -6246,7 +6246,7 @@ dependencies = [ [[package]] name = "timsquery" -version = "0.27.0" +version = "0.28.0" dependencies = [ "array2d", "arrow", @@ -6269,7 +6269,7 @@ dependencies = [ [[package]] name = "timsquery_cli" -version = "0.27.0" +version = "0.28.0" dependencies = [ "clap", "half", @@ -6288,7 +6288,7 @@ dependencies = [ [[package]] name = "timsquery_pyo3" -version = "0.27.0" +version = "0.28.0" dependencies = [ "numpy", "pyo3", @@ -6299,7 +6299,7 @@ dependencies = [ [[package]] name = "timsquery_viewer" -version = "0.27.0" +version = "0.28.0" dependencies = [ "calibrt", "clap", @@ -6343,7 +6343,7 @@ dependencies = [ [[package]] name = "timsseek" -version = "0.27.0" +version = "0.28.0" dependencies = [ "arrow", "calibrt", @@ -6367,7 +6367,7 @@ dependencies = [ [[package]] name = "timsseek_cli" -version = "0.27.0" +version = "0.28.0" dependencies = [ "clap", "indicatif", diff --git a/Cargo.toml b/Cargo.toml index c42dbcd..939f3d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ default-members = [ ] [workspace.package] -version = "0.27.0" +version = "0.28.0" edition = "2024" authors = ["Sebastian Paez"] license = "Apache-2.0" diff --git a/Taskfile.yml b/Taskfile.yml index ba7a0b7..c1b5953 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -70,7 +70,7 @@ tasks: 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" + - 'echo "Check readiness: curl -s http://localhost:8501/v2/health/ready"' speclib:stop-koina: desc: Stop local Koina server diff --git a/pyproject.toml b/pyproject.toml index 7235c5f..d1f26dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "timsseek-workspace" -version = "0.27.0" +version = "0.28.0" requires-python = ">=3.11,<3.13" dependencies = [] @@ -31,7 +31,7 @@ preview = true select = ["E", "F", "T20", "I"] [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/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/uv.lock b/uv.lock index f37d0c9..e4d7198 100644 --- a/uv.lock +++ b/uv.lock @@ -682,7 +682,7 @@ wheels = [ [[package]] name = "timsseek-workspace" -version = "0.27.0" +version = "0.28.0" source = { virtual = "." } [package.dev-dependencies]