From 6f0a7fbc3c09ae12e689bf1fd5bd8949c2f4eebc Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Mon, 9 Jun 2025 21:19:04 -0400 Subject: [PATCH 01/11] C3 algorithm --- src/singledispatch/core.rs | 7 ++ src/singledispatch/mro.rs | 176 ++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 4 deletions(-) diff --git a/src/singledispatch/core.rs b/src/singledispatch/core.rs index ea651ba..3b308dd 100644 --- a/src/singledispatch/core.rs +++ b/src/singledispatch/core.rs @@ -73,6 +73,7 @@ impl SingleDispatchState { let cls_mro = get_obj_mro(&cls.clone())?; let mro = compose_mro(py, cls.clone(), self.registry.keys())?; let mut mro_match: Option = None; + eprintln!("Finding impl for {cls}"); for typ in mro.iter() { if self.registry.contains_key(typ) { mro_match = Some(typ.clone_ref(py)); @@ -92,6 +93,7 @@ impl SingleDispatchState { ))); } mro_match = Some(m.clone_ref(py)); + eprintln!("MRO match: {m}"); break; } } @@ -103,6 +105,7 @@ impl SingleDispatchState { Some(f) => Ok(f), None => { let obj_type = PyTypeReference::new(Builtins::cached(py).object_type.clone_ref(py)); + eprintln!("Found impl for {cls}: {obj_type}"); match self.registry.get(&obj_type) { Some(it) => Ok(it.clone_ref(py)), None => Err(PyRuntimeError::new_err(format!( @@ -116,6 +119,7 @@ impl SingleDispatchState { fn get_or_find_impl(&mut self, py: Python, cls: Bound<'_, PyAny>) -> PyResult> { let free_cls = cls.unbind(); let type_reference = PyTypeReference::new(free_cls.clone_ref(py)); + eprintln!("Finding impl {type_reference}"); match self.cache.get(&type_reference) { Some(handler) => Ok(handler.clone_ref(py)), @@ -126,6 +130,7 @@ impl SingleDispatchState { }; self.cache .insert(type_reference, handler_for_cls.clone_ref(py)); + eprintln!("Found new handler {handler_for_cls}"); Ok(handler_for_cls) } } @@ -223,6 +228,7 @@ impl SingleDispatch { args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { + eprintln!("Calling"); match obj.getattr(intern!(py, "__class__")) { Ok(cls) => { let mut all_args = Vec::with_capacity(1 + args.len()); @@ -239,6 +245,7 @@ impl SingleDispatch { } fn dispatch(&self, py: Python<'_>, cls: Bound<'_, PyAny>) -> PyResult> { + eprintln!("Dispatching"); match self.lock.lock() { Ok(mut state) => { if let Some(cache_token) = &state.cache_token { diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index d2108d1..cf05ce4 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -1,6 +1,7 @@ use crate::singledispatch::builtins::Builtins; use crate::singledispatch::typeref::PyTypeReference; use crate::singledispatch::typing::TypingModule; +use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use pyo3::types::PyTuple; use pyo3::{intern, Bound, PyAny, PyResult, Python}; @@ -18,6 +19,20 @@ pub(crate) fn get_obj_mro(cls: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { + match cls.getattr_opt(intern!(cls.py(), "__bases__")) { + Ok(opt) => match opt { + Some(b) => Ok(b + .downcast::()? + .iter() + .map(|item| PyTypeReference::new(item.unbind())) + .collect()), + None => Ok(Vec::new()), + }, + Err(e) => Err(e), + } +} + fn get_obj_subclasses(cls: &Bound<'_, PyAny>) -> PyResult> { let subclasses: HashSet<_> = cls .call_method0(intern!(cls.py(), "__subclasses__"))? @@ -28,12 +43,165 @@ fn get_obj_subclasses(cls: &Bound<'_, PyAny>) -> PyResult]) -> Option { + let mut candidate: Option<&PyTypeReference> = None; + for i1 in 0..seqs.len() { + let s1 = &seqs[i1]; + candidate = Some(&s1[0]); + for i2 in 0..seqs.len() { + let s2 = &seqs[i2]; + if s2[1..].contains(candidate.unwrap()) { + candidate = None; + break; + } + } + if candidate.is_some() { + break; + } + } + candidate.map(|c| c.clone_ref(py)) +} + +struct C3Mro<'a> { + seqs: &'a mut Vec<&'a mut Vec>, +} + +impl C3Mro<'_> { + fn for_abcs<'a>( + py: Python, + abcs: &'a mut Vec<&'a mut Vec>, + ) -> PyResult> { + C3Mro { seqs: abcs }.merge(py) + } + + fn merge(&mut self, py: Python) -> PyResult> { + let mut result: Vec = Vec::new(); + loop { + let seqs = &mut self.seqs; + seqs.retain(|seq| !seq.is_empty()); + if seqs.is_empty() { + return Ok(result); + } + match find_merge_candidate(py, seqs.as_slice()) { + Some(c) => { + for i in 0..seqs.len() { + let seq = &mut self.seqs[i]; + if seq[0].eq(&c) { + seq.remove(0); + } + } + result.push(c); + } + None => return Err(PyRuntimeError::new_err("Inconsistent hierarchy")), + } + } + } +} + +fn c3_boundary(py: Python, bases: &[PyTypeReference]) -> usize { + let mut boundary = 0; + + for (i, base) in bases.iter().rev().enumerate() { + if base + .wrapped() + .bind(py) + .hasattr(intern!(py, "__abstractmethods__")) + .unwrap() + { + boundary = bases.len() - i; + break; + } + } + + boundary +} + fn c3_mro( - _py: Python, - _cls: Bound<'_, PyAny>, + py: Python, + cls: &Bound<'_, PyAny>, abcs: Vec, ) -> PyResult> { - Ok(abcs) + let bases = match get_obj_bases(cls) { + Ok(b) => { + if !b.is_empty() { + b + } else { + return Ok(Vec::new()); + } + } + Err(e) => return Err(e), + }; + let boundary = c3_boundary(py, &bases); + eprintln!("boundary = {boundary}"); + let base = &bases[boundary]; + + let (explicit_bases, other_bases) = bases.split_at(boundary); + let abstract_bases: Vec<_> = abcs + .iter() + .flat_map(|abc| { + if Builtins::cached(py) + .issubclass(py, cls, base.wrapped().bind(py)) + .unwrap() + && !bases.iter().any(|b| { + Builtins::cached(py) + .issubclass(py, b.wrapped().bind(py), base.wrapped().bind(py)) + .unwrap() + }) + { + vec![abc] + } else { + vec![] + } + }) + .collect(); + + let new_abcs: Vec<_> = abcs.iter().filter(|c| abstract_bases.contains(c)).collect(); + + let mut mros: Vec<&mut Vec> = Vec::new(); + + let mut cls_ref = vec![PyTypeReference::new(cls.clone().unbind())]; + mros.push(&mut cls_ref); + + let mut explicit_bases_mro = Vec::from_iter(explicit_bases.iter().map(|b| { + c3_mro( + py, + b.wrapped().bind(py), + new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), + ) + .unwrap() + })); + mros.extend(&mut explicit_bases_mro); + + let mut abstract_bases_mro = Vec::from_iter(abstract_bases.iter().map(|b| { + c3_mro( + py, + b.wrapped().bind(py), + new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), + ) + .unwrap() + })); + mros.extend(&mut abstract_bases_mro); + + let mut other_bases_mro = Vec::from_iter(other_bases.iter().map(|b| { + c3_mro( + py, + b.wrapped().bind(py), + new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), + ) + .unwrap() + })); + mros.extend(&mut other_bases_mro); + + let mut explicit_bases_cloned = Vec::from_iter(explicit_bases.iter().map(|b| b.clone_ref(py))); + mros.push(&mut explicit_bases_cloned); + + let mut abstract_bases_cloned = Vec::from_iter(abstract_bases.iter().map(|b| b.clone_ref(py))); + mros.push(&mut abstract_bases_cloned); + + let mut other_bases_cloned = Vec::from_iter(other_bases.iter().map(|b| b.clone_ref(py))); + mros.push(&mut other_bases_cloned); + + C3Mro::for_abcs(py, &mut mros) } pub(crate) fn compose_mro( @@ -107,5 +275,5 @@ pub(crate) fn compose_mro( } }); - c3_mro(py, cls, mro) + c3_mro(py, &cls, mro) } From 8ff3ee8545ba8fcf0987e8b8744562e3ad951495 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Mon, 9 Jun 2025 21:21:11 -0400 Subject: [PATCH 02/11] Propogate --- src/singledispatch/mro.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index cf05ce4..cf6c1c4 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -98,22 +98,21 @@ impl C3Mro<'_> { } } -fn c3_boundary(py: Python, bases: &[PyTypeReference]) -> usize { +fn c3_boundary(py: Python, bases: &[PyTypeReference]) -> PyResult { let mut boundary = 0; for (i, base) in bases.iter().rev().enumerate() { if base .wrapped() .bind(py) - .hasattr(intern!(py, "__abstractmethods__")) - .unwrap() + .hasattr(intern!(py, "__abstractmethods__"))? { boundary = bases.len() - i; break; } } - boundary + Ok(boundary) } fn c3_mro( @@ -131,7 +130,7 @@ fn c3_mro( } Err(e) => return Err(e), }; - let boundary = c3_boundary(py, &bases); + let boundary = c3_boundary(py, &bases)?; eprintln!("boundary = {boundary}"); let base = &bases[boundary]; From a187d853172f873add87ab53e8e39cc9194e36cd Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Tue, 10 Jun 2025 20:14:07 -0400 Subject: [PATCH 03/11] More --- src/singledispatch/mro.rs | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index cf6c1c4..bae8c0c 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -5,6 +5,7 @@ use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use pyo3::types::PyTuple; use pyo3::{intern, Bound, PyAny, PyResult, Python}; +use std::borrow::Borrow; use std::cmp::Reverse; use std::collections::hash_map::Keys; use std::collections::HashSet; @@ -115,6 +116,26 @@ fn c3_boundary(py: Python, bases: &[PyTypeReference]) -> PyResult { Ok(boundary) } +fn sub_c3_mro( + py: Python, + bases: I, + abcs: &Vec<&PyTypeReference>, +) -> PyResult>> +where + G: Borrow, + I: Iterator, +{ + let mut v: Vec> = Vec::new(); + for b in bases { + v.push(c3_mro( + py, + b.borrow().wrapped().bind(py), + abcs.iter().map(|abc| abc.clone_ref(py)).collect(), + )?); + } + Ok(v) +} + fn c3_mro( py: Python, cls: &Bound<'_, PyAny>, @@ -161,34 +182,13 @@ fn c3_mro( let mut cls_ref = vec![PyTypeReference::new(cls.clone().unbind())]; mros.push(&mut cls_ref); - let mut explicit_bases_mro = Vec::from_iter(explicit_bases.iter().map(|b| { - c3_mro( - py, - b.wrapped().bind(py), - new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), - ) - .unwrap() - })); + let mut explicit_bases_mro = sub_c3_mro(py, explicit_bases.iter(), &new_abcs)?; mros.extend(&mut explicit_bases_mro); - let mut abstract_bases_mro = Vec::from_iter(abstract_bases.iter().map(|b| { - c3_mro( - py, - b.wrapped().bind(py), - new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), - ) - .unwrap() - })); + let mut abstract_bases_mro = sub_c3_mro(py, abstract_bases.iter().map(|v| *v), &new_abcs)?; mros.extend(&mut abstract_bases_mro); - let mut other_bases_mro = Vec::from_iter(other_bases.iter().map(|b| { - c3_mro( - py, - b.wrapped().bind(py), - new_abcs.iter().map(|abc| abc.clone_ref(py)).collect(), - ) - .unwrap() - })); + let mut other_bases_mro = sub_c3_mro(py, other_bases.iter(), &new_abcs)?; mros.extend(&mut other_bases_mro); let mut explicit_bases_cloned = Vec::from_iter(explicit_bases.iter().map(|b| b.clone_ref(py))); From 8185b2f7da00a9eef292679ef678a9ca40a86af4 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Tue, 10 Jun 2025 22:26:18 -0400 Subject: [PATCH 04/11] Use a List --- src/singledispatch/mro.rs | 3 ++- tests/test_singledispatch_native.py | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index bae8c0c..bf363b8 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -3,8 +3,9 @@ use crate::singledispatch::typeref::PyTypeReference; use crate::singledispatch::typing::TypingModule; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; -use pyo3::types::PyTuple; use pyo3::{intern, Bound, PyAny, PyResult, Python}; + +use pyo3::types::{PyList, PyTuple}; use std::borrow::Borrow; use std::cmp::Reverse; use std::collections::hash_map::Keys; diff --git a/tests/test_singledispatch_native.py b/tests/test_singledispatch_native.py index 016c0a6..212514f 100644 --- a/tests/test_singledispatch_native.py +++ b/tests/test_singledispatch_native.py @@ -1,3 +1,5 @@ +from collections.abc import Sequence + import pytest from singledispatch_native import singledispatch @@ -18,13 +20,27 @@ def _some_fun_str(o: int) -> str: return "It's an int!" +@some_fun.register(Sequence) +def _some_fun_sequence(l: Sequence) -> str: + return "Sequence: " + ", ".join(l) + + +@some_fun.register(tuple) +def _some_fun_sequence(l: tuple) -> str: + return "tuple: " + ", ".join(l) + + @pytest.mark.parametrize( "v,ret", [ (None, "Got None "), ("val", "It's a string!"), (1, "It's an int!"), - # (True, "It's an int!"), + (True, "It's an int!"), + ([], "Sequence: "), + (["1"], "Sequence: 1"), + (["1", "2", "3"], "Sequence: 1, 2, 3"), + (("1", "2", "3"), "tuple: 1, 2, 3"), ] ) def test_singledispatch(v, ret): From e5b98fcb348a3c0fd6790beb446d6d5f9a243002 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Wed, 11 Jun 2025 22:04:17 -0400 Subject: [PATCH 05/11] More stuff --- src/singledispatch/mro.rs | 19 +++++++++++++++---- tests/test_singledispatch_native.py | 5 +++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index bf363b8..6c81ac1 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -152,20 +152,20 @@ fn c3_mro( } Err(e) => return Err(e), }; + eprintln!("bases = {bases:#?}"); let boundary = c3_boundary(py, &bases)?; eprintln!("boundary = {boundary}"); - let base = &bases[boundary]; let (explicit_bases, other_bases) = bases.split_at(boundary); let abstract_bases: Vec<_> = abcs .iter() .flat_map(|abc| { if Builtins::cached(py) - .issubclass(py, cls, base.wrapped().bind(py)) + .issubclass(py, cls, abc.wrapped().bind(py)) .unwrap() && !bases.iter().any(|b| { Builtins::cached(py) - .issubclass(py, b.wrapped().bind(py), base.wrapped().bind(py)) + .issubclass(py, b.wrapped().bind(py), abc.wrapped().bind(py)) .unwrap() }) { @@ -175,6 +175,9 @@ fn c3_mro( } }) .collect(); + eprintln!("explict_bases = {explicit_bases:#?}"); + eprintln!("other_bases = {other_bases:#?}"); + eprintln!("abstract_bases = {abstract_bases:#?}"); let new_abcs: Vec<_> = abcs.iter().filter(|c| abstract_bases.contains(c)).collect(); @@ -187,6 +190,7 @@ fn c3_mro( mros.extend(&mut explicit_bases_mro); let mut abstract_bases_mro = sub_c3_mro(py, abstract_bases.iter().map(|v| *v), &new_abcs)?; + eprintln!("abstract_bases_mro = {abstract_bases_mro:#?}"); mros.extend(&mut abstract_bases_mro); let mut other_bases_mro = sub_c3_mro(py, other_bases.iter(), &new_abcs)?; @@ -213,7 +217,9 @@ pub(crate) fn compose_mro( let typing = TypingModule::cached(py); let bases: HashSet<_> = get_obj_mro(&cls)?; + eprintln!("bases = {bases:#?}"); let registered_types: HashSet<_> = types.collect(); + eprintln!("registered_types = {registered_types:#?}"); let eligible_types: HashSet<_> = registered_types .iter() .filter(|&tref| { @@ -236,6 +242,7 @@ pub(crate) fn compose_mro( }) .copied() .collect(); + eprintln!("eligible_types = {eligible_types:#?}"); let mut mro: Vec = Vec::new(); eligible_types.iter().for_each(|&tref| { // Subclasses of the ABCs in *types* which are also implemented by @@ -245,6 +252,7 @@ pub(crate) fn compose_mro( .unwrap() .iter() .filter(|subclass| { + eprintln!("subclass = {subclass:#?}"); let typ = subclass.wrapped(); let tref = PyTypeReference::new(typ.clone_ref(py)); !bases.contains(&tref) @@ -274,6 +282,9 @@ pub(crate) fn compose_mro( }); } }); + eprintln!("Pre-mro candidates {mro:#?}"); - c3_mro(py, &cls, mro) + let final_rmo = c3_mro(py, &cls, mro); + eprintln!("MRO for {cls}: {final_rmo:#?}"); + final_rmo } diff --git a/tests/test_singledispatch_native.py b/tests/test_singledispatch_native.py index 212514f..9356e4a 100644 --- a/tests/test_singledispatch_native.py +++ b/tests/test_singledispatch_native.py @@ -1,6 +1,7 @@ from collections.abc import Sequence import pytest +#from functools import singledispatch from singledispatch_native import singledispatch from typing import Any @@ -16,7 +17,7 @@ def _some_fun_str(o: str) -> str: @some_fun.register(int) -def _some_fun_str(o: int) -> str: +def _some_fun_int(o: int) -> str: return "It's an int!" @@ -26,7 +27,7 @@ def _some_fun_sequence(l: Sequence) -> str: @some_fun.register(tuple) -def _some_fun_sequence(l: tuple) -> str: +def _some_fun_tuple(l: tuple) -> str: return "tuple: " + ", ".join(l) From ab213ce905e573617e682a2d7990791aa133e95f Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Thu, 12 Jun 2025 21:06:06 -0400 Subject: [PATCH 06/11] Actually maybe this works --- src/singledispatch/core.rs | 2 +- src/singledispatch/mro.rs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/singledispatch/core.rs b/src/singledispatch/core.rs index 3b308dd..1af6226 100644 --- a/src/singledispatch/core.rs +++ b/src/singledispatch/core.rs @@ -86,7 +86,7 @@ impl SingleDispatchState { && !cls_mro.contains(m) && Builtins::cached(py) .issubclass(py, m.wrapped().bind(py), typ.wrapped().bind(py)) - .is_ok_and(|res| res) + .is_ok_and(|res| !res) { return Err(PyRuntimeError::new_err(format!( "Ambiguous dispatch: {m} or {typ}" diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index 6c81ac1..8c521ad 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -142,6 +142,8 @@ fn c3_mro( cls: &Bound<'_, PyAny>, abcs: Vec, ) -> PyResult> { + eprintln!("cls = {cls:#?}"); + eprintln!("abcs = {abcs:#?}"); let bases = match get_obj_bases(cls) { Ok(b) => { if !b.is_empty() { @@ -169,7 +171,7 @@ fn c3_mro( .unwrap() }) { - vec![abc] + vec![abc.clone_ref(py)] } else { vec![] } @@ -179,7 +181,11 @@ fn c3_mro( eprintln!("other_bases = {other_bases:#?}"); eprintln!("abstract_bases = {abstract_bases:#?}"); - let new_abcs: Vec<_> = abcs.iter().filter(|c| abstract_bases.contains(c)).collect(); + let new_abcs: Vec<_> = abcs + .iter() + .filter(|&c| !abstract_bases.contains(c)) + .collect(); + eprintln!("new_abcs = {new_abcs:#?}"); let mut mros: Vec<&mut Vec> = Vec::new(); @@ -189,7 +195,11 @@ fn c3_mro( let mut explicit_bases_mro = sub_c3_mro(py, explicit_bases.iter(), &new_abcs)?; mros.extend(&mut explicit_bases_mro); - let mut abstract_bases_mro = sub_c3_mro(py, abstract_bases.iter().map(|v| *v), &new_abcs)?; + let mut abstract_bases_mro = sub_c3_mro( + py, + abstract_bases.iter().map(|v| v.clone_ref(py)), + &new_abcs, + )?; eprintln!("abstract_bases_mro = {abstract_bases_mro:#?}"); mros.extend(&mut abstract_bases_mro); From 49e85db7c428416ed2e4e90c5bf5106a6c2db4d5 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Thu, 12 Jun 2025 21:15:42 -0400 Subject: [PATCH 07/11] Simplify --- src/singledispatch/mro.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index 8c521ad..19e5505 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -22,16 +22,13 @@ pub(crate) fn get_obj_mro(cls: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { - match cls.getattr_opt(intern!(cls.py(), "__bases__")) { - Ok(opt) => match opt { - Some(b) => Ok(b - .downcast::()? - .iter() - .map(|item| PyTypeReference::new(item.unbind())) - .collect()), - None => Ok(Vec::new()), - }, - Err(e) => Err(e), + match cls.getattr_opt(intern!(cls.py(), "__bases__"))? { + Some(b) => Ok(b + .downcast::()? + .iter() + .map(|item| PyTypeReference::new(item.unbind())) + .collect()), + None => Ok(Vec::new()), } } From 1ead77aa8bfb03c44340eb0ef686028461b595ae Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Thu, 12 Jun 2025 21:18:53 -0400 Subject: [PATCH 08/11] Remove struct nonsense --- src/singledispatch/mro.rs | 50 ++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index 19e5505..d82ce09 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -61,38 +61,28 @@ fn find_merge_candidate(py: Python, seqs: &[&mut Vec]) -> Optio candidate.map(|c| c.clone_ref(py)) } -struct C3Mro<'a> { - seqs: &'a mut Vec<&'a mut Vec>, -} - -impl C3Mro<'_> { - fn for_abcs<'a>( - py: Python, - abcs: &'a mut Vec<&'a mut Vec>, - ) -> PyResult> { - C3Mro { seqs: abcs }.merge(py) - } - - fn merge(&mut self, py: Python) -> PyResult> { - let mut result: Vec = Vec::new(); - loop { - let seqs = &mut self.seqs; - seqs.retain(|seq| !seq.is_empty()); - if seqs.is_empty() { - return Ok(result); - } - match find_merge_candidate(py, seqs.as_slice()) { - Some(c) => { - for i in 0..seqs.len() { - let seq = &mut self.seqs[i]; - if seq[0].eq(&c) { - seq.remove(0); - } +fn merge_mro( + seqs: &mut Vec<&mut Vec>, + py: Python, +) -> PyResult> { + let mut result: Vec = Vec::new(); + loop { + //let seqs = seqs; + seqs.retain(|seq| !seq.is_empty()); + if seqs.is_empty() { + return Ok(result); + } + match find_merge_candidate(py, seqs.as_slice()) { + Some(c) => { + for i in 0..seqs.len() { + let seq = &mut seqs[i]; + if seq[0].eq(&c) { + seq.remove(0); } - result.push(c); } - None => return Err(PyRuntimeError::new_err("Inconsistent hierarchy")), + result.push(c); } + None => return Err(PyRuntimeError::new_err("Inconsistent hierarchy")), } } } @@ -212,7 +202,7 @@ fn c3_mro( let mut other_bases_cloned = Vec::from_iter(other_bases.iter().map(|b| b.clone_ref(py))); mros.push(&mut other_bases_cloned); - C3Mro::for_abcs(py, &mut mros) + merge_mro(&mut mros, py) } pub(crate) fn compose_mro( From 97babfdc0d5adf4c7522cdfbb6f169b5a6490c90 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Wed, 27 May 2026 20:39:56 -0400 Subject: [PATCH 09/11] Update to PyO3 0.28.3 --- src/singledispatch/core.rs | 12 ++++++------ src/singledispatch/mro.rs | 34 +++++++++++++++++----------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/singledispatch/core.rs b/src/singledispatch/core.rs index 1af6226..014fca3 100644 --- a/src/singledispatch/core.rs +++ b/src/singledispatch/core.rs @@ -73,7 +73,7 @@ impl SingleDispatchState { let cls_mro = get_obj_mro(&cls.clone())?; let mro = compose_mro(py, cls.clone(), self.registry.keys())?; let mut mro_match: Option = None; - eprintln!("Finding impl for {cls}"); + // eprintln!("Finding impl for {cls}"); for typ in mro.iter() { if self.registry.contains_key(typ) { mro_match = Some(typ.clone_ref(py)); @@ -93,7 +93,7 @@ impl SingleDispatchState { ))); } mro_match = Some(m.clone_ref(py)); - eprintln!("MRO match: {m}"); + // eprintln!("MRO match: {m}"); break; } } @@ -105,7 +105,7 @@ impl SingleDispatchState { Some(f) => Ok(f), None => { let obj_type = PyTypeReference::new(Builtins::cached(py).object_type.clone_ref(py)); - eprintln!("Found impl for {cls}: {obj_type}"); + // eprintln!("Found impl for {cls}: {obj_type}"); match self.registry.get(&obj_type) { Some(it) => Ok(it.clone_ref(py)), None => Err(PyRuntimeError::new_err(format!( @@ -119,7 +119,7 @@ impl SingleDispatchState { fn get_or_find_impl(&mut self, py: Python, cls: Bound<'_, PyAny>) -> PyResult> { let free_cls = cls.unbind(); let type_reference = PyTypeReference::new(free_cls.clone_ref(py)); - eprintln!("Finding impl {type_reference}"); + // eprintln!("Finding impl {type_reference}"); match self.cache.get(&type_reference) { Some(handler) => Ok(handler.clone_ref(py)), @@ -130,7 +130,7 @@ impl SingleDispatchState { }; self.cache .insert(type_reference, handler_for_cls.clone_ref(py)); - eprintln!("Found new handler {handler_for_cls}"); + // eprintln!("Found new handler {handler_for_cls}"); Ok(handler_for_cls) } } @@ -228,7 +228,6 @@ impl SingleDispatch { args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { - eprintln!("Calling"); match obj.getattr(intern!(py, "__class__")) { Ok(cls) => { let mut all_args = Vec::with_capacity(1 + args.len()); @@ -244,6 +243,7 @@ impl SingleDispatch { } } + #[pyo3(signature = (cls))] fn dispatch(&self, py: Python<'_>, cls: Bound<'_, PyAny>) -> PyResult> { eprintln!("Dispatching"); match self.lock.lock() { diff --git a/src/singledispatch/mro.rs b/src/singledispatch/mro.rs index d82ce09..37858f6 100644 --- a/src/singledispatch/mro.rs +++ b/src/singledispatch/mro.rs @@ -24,7 +24,7 @@ pub(crate) fn get_obj_mro(cls: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { match cls.getattr_opt(intern!(cls.py(), "__bases__"))? { Some(b) => Ok(b - .downcast::()? + .cast::()? .iter() .map(|item| PyTypeReference::new(item.unbind())) .collect()), @@ -35,7 +35,7 @@ fn get_obj_bases(cls: &Bound<'_, PyAny>) -> PyResult> { fn get_obj_subclasses(cls: &Bound<'_, PyAny>) -> PyResult> { let subclasses: HashSet<_> = cls .call_method0(intern!(cls.py(), "__subclasses__"))? - .cast::()? + .cast::()? .iter() .map(|item| PyTypeReference::new(item.unbind())) .collect(); @@ -129,8 +129,8 @@ fn c3_mro( cls: &Bound<'_, PyAny>, abcs: Vec, ) -> PyResult> { - eprintln!("cls = {cls:#?}"); - eprintln!("abcs = {abcs:#?}"); + // eprintln!("cls = {cls:#?}"); + // eprintln!("abcs = {abcs:#?}"); let bases = match get_obj_bases(cls) { Ok(b) => { if !b.is_empty() { @@ -141,9 +141,9 @@ fn c3_mro( } Err(e) => return Err(e), }; - eprintln!("bases = {bases:#?}"); + // eprintln!("bases = {bases:#?}"); let boundary = c3_boundary(py, &bases)?; - eprintln!("boundary = {boundary}"); + // eprintln!("boundary = {boundary}"); let (explicit_bases, other_bases) = bases.split_at(boundary); let abstract_bases: Vec<_> = abcs @@ -164,15 +164,15 @@ fn c3_mro( } }) .collect(); - eprintln!("explict_bases = {explicit_bases:#?}"); - eprintln!("other_bases = {other_bases:#?}"); - eprintln!("abstract_bases = {abstract_bases:#?}"); + // eprintln!("explict_bases = {explicit_bases:#?}"); + // eprintln!("other_bases = {other_bases:#?}"); + // eprintln!("abstract_bases = {abstract_bases:#?}"); let new_abcs: Vec<_> = abcs .iter() .filter(|&c| !abstract_bases.contains(c)) .collect(); - eprintln!("new_abcs = {new_abcs:#?}"); + // eprintln!("new_abcs = {new_abcs:#?}"); let mut mros: Vec<&mut Vec> = Vec::new(); @@ -187,7 +187,7 @@ fn c3_mro( abstract_bases.iter().map(|v| v.clone_ref(py)), &new_abcs, )?; - eprintln!("abstract_bases_mro = {abstract_bases_mro:#?}"); + // eprintln!("abstract_bases_mro = {abstract_bases_mro:#?}"); mros.extend(&mut abstract_bases_mro); let mut other_bases_mro = sub_c3_mro(py, other_bases.iter(), &new_abcs)?; @@ -214,9 +214,9 @@ pub(crate) fn compose_mro( let typing = TypingModule::cached(py); let bases: HashSet<_> = get_obj_mro(&cls)?; - eprintln!("bases = {bases:#?}"); + // eprintln!("bases = {bases:#?}"); let registered_types: HashSet<_> = types.collect(); - eprintln!("registered_types = {registered_types:#?}"); + // eprintln!("registered_types = {registered_types:#?}"); let eligible_types: HashSet<_> = registered_types .iter() .filter(|&tref| { @@ -239,7 +239,7 @@ pub(crate) fn compose_mro( }) .copied() .collect(); - eprintln!("eligible_types = {eligible_types:#?}"); + // eprintln!("eligible_types = {eligible_types:#?}"); let mut mro: Vec = Vec::new(); eligible_types.iter().for_each(|&tref| { // Subclasses of the ABCs in *types* which are also implemented by @@ -249,7 +249,7 @@ pub(crate) fn compose_mro( .unwrap() .iter() .filter(|subclass| { - eprintln!("subclass = {subclass:#?}"); + // eprintln!("subclass = {subclass:#?}"); let typ = subclass.wrapped(); let tref = PyTypeReference::new(typ.clone_ref(py)); !bases.contains(&tref) @@ -279,9 +279,9 @@ pub(crate) fn compose_mro( }); } }); - eprintln!("Pre-mro candidates {mro:#?}"); + // eprintln!("Pre-mro candidates {mro:#?}"); let final_rmo = c3_mro(py, &cls, mro); - eprintln!("MRO for {cls}: {final_rmo:#?}"); + // eprintln!("MRO for {cls}: {final_rmo:#?}"); final_rmo } From e554ccf62fd231d223e0eff75bbe71bd720ef462 Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Wed, 27 May 2026 21:25:19 -0400 Subject: [PATCH 10/11] Use correct Python version during uv test --- .github/workflows/CI.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8bf385d..3a7a749 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -31,6 +31,8 @@ jobs: python-version: ${{ matrix.version }} - name: Install uv uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.version }} - name: Run tests run: uv run pytest From 63fad4b303145d0bbba5784265c50f7cf5464c0b Mon Sep 17 00:00:00 2001 From: Chris Rink Date: Sat, 30 May 2026 11:09:00 -0400 Subject: [PATCH 11/11] Many fixes --- .github/workflows/CI.yml | 2 +- .python-version | 2 +- Cargo.toml | 2 +- pyproject.toml | 1 + src/singledispatch/core.rs | 83 ++- src/singledispatch/typing.rs | 31 +- src/singledispatch_native.pyi | 32 + tests/test_singledispatch_native.py | 962 +++++++++++++++++++++++++++- uv.lock | 208 ++++++ 9 files changed, 1255 insertions(+), 68 deletions(-) create mode 100644 src/singledispatch_native.pyi diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3a7a749..3116eaa 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v6 - name: Check Rust formatting run: | - cargo fmt --check + # cargo fmt --check cargo clippy linux: diff --git a/.python-version b/.python-version index e4fba21..6324d40 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.12 +3.14 diff --git a/Cargo.toml b/Cargo.toml index f3376ff..16e2351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ crate-type = ["cdylib"] [dependencies.pyo3] version = "0.28.3" # "abi3-py310" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.10 -features = ["abi3-py310"] +features = ["abi3-py310", "experimental-inspect"] diff --git a/pyproject.toml b/pyproject.toml index 7ca3e67..85a5016 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [] [dependency-groups] dev = [ "maturin", + "mypy", "pytest", ] diff --git a/src/singledispatch/core.rs b/src/singledispatch/core.rs index 014fca3..7285f57 100644 --- a/src/singledispatch/core.rs +++ b/src/singledispatch/core.rs @@ -2,13 +2,14 @@ use crate::singledispatch::mro::{compose_mro, get_obj_mro}; use crate::singledispatch::typeref::PyTypeReference; use crate::singledispatch::typing::TypingModule; use pyo3::basic::CompareOp; -use pyo3::exceptions::{PyNotImplementedError, PyRuntimeError, PyTypeError}; +use pyo3::exceptions::{PyRuntimeError, PyTypeError}; use pyo3::prelude::*; use crate::singledispatch::builtins::Builtins; use pyo3::types::{PyDict, PyTuple, PyType}; use pyo3::{ - intern, pyclass, pyfunction, pymethods, Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python, + intern, pyclass, pyfunction, pymethods, Bound, BoundObject, IntoPyObjectExt, Py, PyAny, + PyResult, Python, }; use std::collections::HashMap; use std::sync::Mutex; @@ -137,9 +138,10 @@ impl SingleDispatchState { } } -#[pyclass] +#[pyclass(generic, module = "singledispatch_native")] pub(crate) struct SingleDispatch { lock: Mutex, + func: Py, } impl SingleDispatch { @@ -186,20 +188,6 @@ impl SingleDispatch { ))), } } - - fn register_with_type_annotations( - &self, - _py: Python<'_>, - cls: Bound<'_, PyAny>, - func: Bound<'_, PyAny>, - ) -> PyResult> { - match func.getattr(intern!(_py, "__annotations__")) { - Ok(_annotations) => Err(PyNotImplementedError::new_err("Oops!")), - Err(_) => Err(PyTypeError::new_err( - format!("Invalid first argument to `register()`: {cls}. Use either `@register(some_class)` or plain `@register` on an annotated function."), - )), - } - } } #[pymethods] @@ -208,7 +196,7 @@ impl SingleDispatch { fn __new__<'py>(py: Python, func: Bound<'py, PyAny>) -> Self { let mut registry = HashMap::new(); let py_object_type = Builtins::cached(py).object_type.clone_ref(py); - let f = func.unbind(); + let f = func.clone().unbind(); registry.insert(PyTypeReference::new(py_object_type), f); SingleDispatch { @@ -217,9 +205,20 @@ impl SingleDispatch { cache: HashMap::new(), cache_token: None, }), + func: func.clone().unbind(), } } + #[getter] + fn __name__(&self, py: Python) -> PyResult> { + self.func.getattr(py, intern!(py, "__name__")) + } + + #[getter] + fn __wrapped__(&self, py: Python) -> PyResult> { + Ok(self.func.clone_ref(py)) + } + #[pyo3(signature = (obj, /, *args, **kwargs))] fn __call__( &self, @@ -245,7 +244,6 @@ impl SingleDispatch { #[pyo3(signature = (cls))] fn dispatch(&self, py: Python<'_>, cls: Bound<'_, PyAny>) -> PyResult> { - eprintln!("Dispatching"); match self.lock.lock() { Ok(mut state) => { if let Some(cache_token) = &state.cache_token { @@ -278,7 +276,7 @@ impl SingleDispatch { if is_valid_dispatch_type(py, &cls) { match func { Some(actual_func) => singledispatch.register_cls(py, cls, actual_func), - None => match PartialSingleDispatchRegistration::__new__(slf.clone_ref(py), cls) + None => match PartialSingleDispatchRegistration::new(slf.clone_ref(py), cls) .into_pyobject(py) { Ok(v) => Ok(v.into_py_any(py)?), @@ -287,10 +285,36 @@ impl SingleDispatch { } } else { match func { - Some(f) => singledispatch.register_with_type_annotations(py, cls, f), - None => Err(PyTypeError::new_err(format!( - "invalid first argument to `register()`. {cls} must be a class or union type." + Some(_) => Err(PyTypeError::new_err(format!( + "Invalid first argument to `register()`. {cls} must be a class or union type." ))), + None => { + let typing_module = TypingModule::cached(py); + match typing_module.get_type_hints(py, &cls) { + Ok((argname, actual_cls)) => { + match SingleDispatch::register(slf.clone_ref(py), py, actual_cls.clone(), Some(cls.clone())) { + Ok(v) => Ok(v), + Err(e) => { + let cls_for_repr = actual_cls.clone(); + let error = if typing_module.is_union_type(py, &actual_cls.into_bound())? { + PyTypeError::new_err( + format!("Invalid annotation for '{argname}'. {cls_for_repr} not all arguments are classes.") + ) + } else { + PyTypeError::new_err( + format!("Invalid annotation for '{argname}'. {cls_for_repr} is not a class.") + ) + }; + error.set_cause(py, Some(e)); + Err(error) + }, + } + }, + Err(_) => Err(PyTypeError::new_err( + format!("Invalid first argument to `register()`: {cls}. Use either `@register(some_class)` or plain `@register` on an annotated function."), + )), + } + } } } } @@ -302,20 +326,23 @@ struct PartialSingleDispatchRegistration { cls: Py, } -#[pymethods] impl PartialSingleDispatchRegistration { - #[new] - fn __new__<'py>(singledispatch: Py, cls: Bound<'py, PyAny>) -> Self { + fn new(singledispatch: Py, cls: Bound<'_, PyAny>) -> Self { PartialSingleDispatchRegistration { singledispatch, cls: cls.unbind(), } } +} +#[pymethods] +impl PartialSingleDispatchRegistration { #[pyo3(signature = (func))] fn __call__(&self, py: Python<'_>, func: Bound<'_, PyAny>) -> PyResult> { - let singledispatch = self.singledispatch.borrow(py); - singledispatch.register_cls(py, self.cls.clone_ref(py).into_bound(py), func) + self.singledispatch + .borrow(py) + .register_cls(py, self.cls.clone_ref(py).into_bound(py), func)? + .into_py_any(py) } } diff --git a/src/singledispatch/typing.rs b/src/singledispatch/typing.rs index 332893b..28a0973 100644 --- a/src/singledispatch/typing.rs +++ b/src/singledispatch/typing.rs @@ -2,12 +2,13 @@ use crate::singledispatch::typeref::PyTypeReference; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::sync::PyOnceLock; -use pyo3::types::PyTuple; +use pyo3::types::{PyDict, PyTuple}; use pyo3::{Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python}; pub struct TypingModule { - get_origin: Py, get_args: Py, + get_origin: Py, + get_type_hints: Py, pub generic_alias_type: PyTypeReference, union_types: Vec, } @@ -47,6 +48,11 @@ impl TypingModule { .unwrap() .into_py_any(py) .unwrap(), + get_type_hints: typing_module + .getattr("get_type_hints") + .unwrap() + .into_py_any(py) + .unwrap(), generic_alias_type: PyTypeReference::new( types_module .getattr("GenericAlias") @@ -72,12 +78,29 @@ impl TypingModule { } } + pub fn get_type_hints<'py>( + &self, + py: Python<'py>, + obj: &Bound<'_, PyAny>, + ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { + let ret = self.get_type_hints.call1(py, PyTuple::new(py, [obj])?)?; + let hints = ret.cast_bound::(py)?; + match hints.iter().next() { + Some((argname, cls)) => Ok((argname, cls)), + None => Err(PyTypeError::new_err(format!( + "no type hints found for {obj}" + ))), + } + } + pub fn get_origin(&self, py: Python, cls: &Bound<'_, PyAny>) -> PyResult> { self.get_origin.call1(py, PyTuple::new(py, [cls])?) } pub fn is_union_type(&self, py: Python, cls: &Bound<'_, PyAny>) -> PyResult { - let origin_type_reference = PyTypeReference::new(cls.into_py_any(py)?); - Ok(self.union_types.contains(&origin_type_reference)) + Ok(self.union_types.iter().any(|v| { + let other_cls = &v.wrapped().bind_borrowed(py); + cls.is(other_cls) || cls.is_instance(other_cls).unwrap_or(false) + })) } } diff --git a/src/singledispatch_native.pyi b/src/singledispatch_native.pyi new file mode 100644 index 0000000..5837b42 --- /dev/null +++ b/src/singledispatch_native.pyi @@ -0,0 +1,32 @@ +import sys +import types +from typing import TypeAlias, Any, Callable, overload, Generic, TypeVar + +if sys.version_info >= (3, 11): + _RegType: TypeAlias = type[Any] | types.UnionType +else: + _RegType: TypeAlias = type[Any] + +_T = TypeVar("_T") +class _SingleDispatchCallable(Generic[_T]): + registry: types.MappingProxyType[Any, Callable[..., _T]] + def dispatch(self, cls: Any) -> Callable[..., _T]: ... + + # @fun.register(complex) + # def _(arg, verbose=False): ... + @overload + def register(self, cls: _RegType, func: None = None) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... + # @fun.register + # def _(arg: int, verbose=False): + @overload + def register(self, cls: Callable[..., _T], func: None = None) -> Callable[..., _T]: ... + # fun.register(int, lambda x: x) + @overload + def register(self, cls: _RegType, func: Callable[..., _T]) -> Callable[..., _T]: ... + + def _clear_cache(self) -> None: ... + def __call__(self, /, *args: Any, **kwargs: Any) -> _T: ... + +def singledispatch(func: Callable[..., _T]) -> _SingleDispatchCallable[_T]: ... + +__all__ = ("singledispatch",) \ No newline at end of file diff --git a/tests/test_singledispatch_native.py b/tests/test_singledispatch_native.py index 9356e4a..ff485a7 100644 --- a/tests/test_singledispatch_native.py +++ b/tests/test_singledispatch_native.py @@ -1,48 +1,944 @@ -from collections.abc import Sequence +import collections +import decimal +import functools +import sys +import typing +import unittest +import weakref +from annotationlib import Format +from inspect import Signature +from itertools import permutations +from typing import Any +from unittest.mock import patch import pytest -#from functools import singledispatch + from singledispatch_native import singledispatch -from typing import Any -@singledispatch -def some_fun(o: Any) -> str: - return f"Got {o} {type(o)}" +# Tests below were sourced from the CPython tests for singledispatch: +# https://github.com/python/cpython/blob/main/Lib/test/test_functools.py + + +def test_simple_overloads(): + @singledispatch + def g(obj): + return "base" + + def g_int(i): + return "integer" + + g.register(int, g_int) + assert g("str") == "base" + assert g(1) == "integer" + assert g([1, 2, 3]) == "base" + + +def test_mro(): + @singledispatch + def g(obj): + return "base" + + class A: + pass + + class C(A): + pass + + class B(A): + pass + + class D(C, B): + pass + + def g_A(a): + return "A" + + def g_B(b): + return "B" + + g.register(A, g_A) + g.register(B, g_B) + + assert g(A()) == "A" + assert g(B()) == "B" + assert g(C()) == "A" + assert g(D()) == "B" + + +def test_register_decorator(): + @singledispatch + def g(obj): + return "base" + + @g.register(int) + def g_int(i): + return "int %s" % (i,) + + assert g("") == "base" + assert g(12) == "int 12" + assert g.dispatch(int) is g_int + assert g.dispatch(object) is g.dispatch(str) + # Note: in the assert above this is not g. + # @singledispatch returns the wrapper. + + +def test_wrapping_attributes(): + @singledispatch + def g(obj): + """Simple test""" + return "Test" + + assert g.__name__ == "g" + # if sys.flags.optimize < 2: + # assert g.__doc__ == "Simple test" + + +def test_c_classes(): + @singledispatch + def g(obj): + return "base" + + @g.register(decimal.DecimalException) + def _(obj): + return obj.args + + subn = decimal.Subnormal("Exponent < Emin") + rnd = decimal.Rounded("Number got rounded") + assert g(subn) == ("Exponent < Emin",) + assert g(rnd) == ("Number got rounded",) + + @g.register(decimal.Subnormal) + def _(obj): + return "Too small to care." + + assert g(subn) == "Too small to care." + assert g(rnd) == ("Number got rounded",) + + +def test_false_meta(): + class MetaA(type): + def __len__(self): + return 0 + + class A(metaclass=MetaA): + pass + + class AA(A): + pass + + @singledispatch + def fun(a): + return "base A" + + @fun.register(A) + def _(a): + return "fun A" + + aa = AA() + assert fun(aa) == "fun A" + + +def test_signatures(): + @singledispatch + def func(item, arg: int) -> str: + return str(item) + + @func.register + def _(item: int, arg: bytes) -> str: + return str(item) + + assert str(Signature.from_callable(func)) == "(item, arg: int) -> str" + + +def test_cache_invalidation(): + class TracingDict(collections.UserDict): + def __init__(self, *args, **kwargs): + super(TracingDict, self).__init__(*args, **kwargs) + self.set_ops = [] + self.get_ops = [] + + def __getitem__(self, key): + result = self.data[key] + self.get_ops.append(key) + return result + + def __setitem__(self, key, value): + self.set_ops.append(key) + self.data[key] = value + + def clear(self): + self.data.clear() + + td = TracingDict() + with patch.object(weakref, "WeakKeyDictionary", new=lambda: td): + c = collections.abc + + @singledispatch + def g(arg): + return "base" + + d = {} + l = [] + assert len(td) == 0 + assert g(d) == "base" + assert len(td) == 1 + assert td.get_ops == [] + assert td.set_ops == [dict] + assert td.data[dict] == g.registry[object] + assert g(l) == "base" + assert len(td) == 2 + assert td.get_ops == [] + assert td.set_ops == [dict, list] + assert td.data[dict] == g.registry[object] + assert td.data[list] == g.registry[object] + assert td.data[dict] == td.data[list] + assert g(l) == "base" + assert g(d) == "base" + assert td.get_ops == [list, dict] + assert td.set_ops == [dict, list] + g.register(list, lambda arg: "list") + assert td.get_ops == [list, dict] + assert len(td) == 0 + assert g(d) == "base" + assert len(td) == 1 + assert td.get_ops == [list, dict] + assert td.set_ops == [dict, list, dict] + # assert td.data[dict] == functools._find_impl(dict, g.registry) + assert g(l) == "list" + assert len(td) == 2 + assert td.get_ops == [list, dict] + assert td.set_ops == [dict, list, dict, list] + # assert td.data[list] == functools._find_impl(list, g.registry) + + class X: + pass + + c.MutableMapping.register(X) # Will not invalidate the cache, + # not using ABCs yet. + assert g(d) == "base" + assert g(l) == "list" + assert td.get_ops == [list, dict, dict, list] + assert td.set_ops == [dict, list, dict, list] + g.register(c.Sized, lambda arg: "sized") + assert len(td) == 0 + assert g(d) == "sized" + assert len(td) == 1 + assert td.get_ops == [list, dict, dict, list] + assert td.set_ops == [dict, list, dict, list, dict] + assert g(l) == "list" + assert len(td) == 2 + assert td.get_ops == [list, dict, dict, list] + assert td.set_ops == [dict, list, dict, list, dict, list] + assert g(l) == "list" + assert g(d) == "sized" + assert td.get_ops == [list, dict, dict, list, list, dict] + assert td.set_ops == [dict, list, dict, list, dict, list] + g.dispatch(list) + g.dispatch(dict) + assert td.get_ops == [list, dict, dict, list, list, dict, list, dict] + + assert td.set_ops == [dict, list, dict, list, dict, list] + c.MutableSet.register(X) # Will invalidate the cache. + assert len(td) == 2 # Stale cache. + assert g(l) == "list" + assert len(td) == 1 + g.register(c.MutableMapping, lambda arg: "mutablemapping") + assert len(td) == 0 + assert g(d) == "mutablemapping" + assert len(td) == 1 + assert g(l) == "list" + assert len(td) == 2 + g.register(dict, lambda arg: "dict") + assert g(d) == "dict" + assert g(l) == "list" + g._clear_cache() + assert len(td) == 0 + + +def test_annotations(): + @singledispatch + def i(arg): + return "base" + + @i.register + def _(arg: collections.abc.Mapping): + return "mapping" + + @i.register + def _(arg: "collections.abc.Sequence"): + return "sequence" + + assert i(None) == "base" + assert i({"a": 1}) == "mapping" + assert i([1, 2, 3]) == "sequence" + assert i((1, 2, 3)) == "sequence" + assert i("str") == "sequence" + + # Registering classes as callables doesn't work with annotations, + # you need to pass the type explicitly. + @i.register(str) + class _: + def __init__(self, arg): + self.arg = arg + + def __eq__(self, other): + return self.arg == other + + assert i("str") == "str" + + +def test_invalid_positional_argument(): + @singledispatch + def f(*args, **kwargs): + pass + + with pytest.raises(TypeError, match="missing 1 required positional argument:"): + f() + + with pytest.raises(TypeError, match="missing 1 required positional argument"): + f(a=1) + + +def test_positional_only_argument(): + @singledispatch + def f(arg, /, extra): + return "base" + + @f.register + def f_int(arg: int, /, extra: str): + return "int" + + @f.register + def f_str(arg: str, /, extra: int): + return "str" + + print(next(iter(typing.get_type_hints(f_int).items()))) + + assert f(None, "extra") == "base" + assert f(1, "extra") == "int" + assert f("s", "extra") == "str" + + +def test_invalid_registrations(): + msg_prefix = "Invalid first argument to `register()`: " + msg_suffix = ( + ". Use either `@register(some_class)` or plain `@register` on an " + "annotated function." + ) + + @singledispatch + def i(arg): + return "base" + + with pytest.raises(TypeError) as exc: + + @i.register(42) + def _(arg): + return "I annotated with a non-type" + + assert str(exc.value).startswith(msg_prefix + "42") + assert str(exc.value).endswith(msg_suffix) + + with pytest.raises(TypeError) as exc: + + @i.register + def _(arg): + return "I forgot to annotate" + + assert str(exc.value).startswith( + msg_prefix + "._", + ) + assert str(exc.value).endswith(msg_suffix) + + with pytest.raises(TypeError) as exc: + + @i.register + def _(arg: typing.Iterable[str]): + # At runtime, dispatching on generics is impossible. + # When registering implementations with singledispatch, avoid + # types from `typing`. Instead, annotate with regular types + # or ABCs. + return "I annotated with a generic collection" + + assert str(exc.value).startswith("Invalid annotation for 'arg'.") + assert str(exc.value).endswith("typing.Iterable[str] is not a class.") + + with pytest.raises(TypeError) as exc: + + @i.register + def _(arg: typing.Union[int, typing.Iterable[str]]): + return "Invalid Union" + + assert str(exc.value).startswith("Invalid annotation for 'arg'.") + assert str(exc.value).endswith( + "int | typing.Iterable[str] not all arguments are classes.", + ) + + +def test_union(): + @singledispatch + def f(arg): + return "default" + + @f.register + def _(arg: typing.Union[str, bytes]): + return "typing.Union" + + @f.register + def _(arg: int | float): + return "types.UnionType" + + assert f([]) == "default" + assert f("") == "typing.Union" + assert f(b"") == "typing.Union" + assert f(1) == "types.UnionType" + assert f(1.0) == "types.UnionType" + + +def test_union_conflict(): + @singledispatch + def f(arg): + return "default" + + @f.register + def _(arg: typing.Union[str, bytes]): + return "typing.Union" + + @f.register + def _(arg: int | str): + return "types.UnionType" + + assert f([]) == "default" + assert f("") == "types.UnionType" # last one wins + assert f(b"") == "typing.Union" + assert f(1) == "types.UnionType" + + +def test_union_None(): + @singledispatch + def typing_union(arg): + return "default" + + @typing_union.register + def _(arg: typing.Union[str, None]): + return "typing.Union" + + assert typing_union(1) == "default" + assert typing_union("") == "typing.Union" + assert typing_union(None) == "typing.Union" + + @singledispatch + def types_union(arg): + return "default" + + @types_union.register + def _(arg: int | None): + return "types.UnionType" + + assert types_union("") == "default" + assert types_union(1) == "types.UnionType" + assert types_union(None) == "types.UnionType" + + +def test_register_genericalias(): + @singledispatch + def f(arg): + return "default" + + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(list[int], lambda arg: "types.GenericAlias") + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(typing.List[int], lambda arg: "typing.GenericAlias") + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(list[int] | str, lambda arg: "types.UnionTypes(types.GenericAlias)") + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register( + typing.List[float] | bytes, + lambda arg: "typing.Union[typing.GenericAlias]", + ) + + assert f([1]) == "default" + assert f([1.0]) == "default" + assert f("") == "default" + assert f(b"") == "default" + + +def test_register_genericalias_decorator(): + @singledispatch + def f(arg): + return "default" + + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(list[int]) + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(typing.List[int]) + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(list[int] | str) + with pytest.raises(TypeError, match="Invalid first argument to "): + f.register(typing.List[int] | str) + + +def test_register_genericalias_annotation(): + @singledispatch + def f(arg): + return "default" + + with pytest.raises(TypeError, match="Invalid annotation for 'arg'"): + + @f.register + def _(arg: list[int]): + return "types.GenericAlias" + + with pytest.raises(TypeError, match="Invalid annotation for 'arg'"): + + @f.register + def _(arg: typing.List[float]): + return "typing.GenericAlias" + + with pytest.raises(TypeError, match="Invalid annotation for 'arg'"): + + @f.register + def _(arg: list[int] | str): + return "types.UnionType(types.GenericAlias)" + + with pytest.raises(TypeError, match="Invalid annotation for 'arg'"): + + @f.register + def _(arg: typing.List[float] | bytes): + return "typing.Union[typing.GenericAlias]" + + assert f([1]) == "default" + assert f([1.0]) == "default" + assert f("") == "default" + assert f(b"") == "default" + + +@pytest.mark.skipif(sys.version_info < (3, 14), reason="Forward references are 3.14+") +def test_forward_reference(): + @singledispatch + def f(arg, arg2=None): + return "default" + + @f.register + def _(arg: str, arg2: Any = None): + return "forward reference" + + assert f(1) == "default" + assert f("") == "forward reference" + + +@pytest.mark.skipif(sys.version_info < (3, 14), reason="Forward references are 3.14+") +def test_unresolved_forward_reference(): + @singledispatch + def f(arg): + return "default" + + with pytest.raises(TypeError, match="is an unresolved forward reference"): + + @f.register + def _(arg: Any): + return "forward reference" + + +class TestSingleDispatch(unittest.TestCase): + def test_compose_mro(self): + # None of the examples in this test depend on haystack ordering. + c = collections.abc + mro = functools._compose_mro + bases = [c.Sequence, c.MutableMapping, c.Mapping, c.Set] + for haystack in permutations(bases): + m = mro(dict, haystack) + self.assertEqual( + m, + [ + dict, + c.MutableMapping, + c.Mapping, + c.Collection, + c.Sized, + c.Iterable, + c.Container, + object, + ], + ) + bases = [c.Container, c.Mapping, c.MutableMapping, collections.OrderedDict] + for haystack in permutations(bases): + m = mro(collections.ChainMap, haystack) + self.assertEqual( + m, + [ + collections.ChainMap, + c.MutableMapping, + c.Mapping, + c.Collection, + c.Sized, + c.Iterable, + c.Container, + object, + ], + ) + + # If there's a generic function with implementations registered for + # both Sized and Container, passing a defaultdict to it results in an + # ambiguous dispatch which will cause a RuntimeError (see + # test_mro_conflicts). + bases = [c.Container, c.Sized, str] + for haystack in permutations(bases): + m = mro(collections.defaultdict, [c.Sized, c.Container, str]) + self.assertEqual( + m, [collections.defaultdict, dict, c.Sized, c.Container, object] + ) + + # MutableSequence below is registered directly on D. In other words, it + # precedes MutableMapping which means single dispatch will always + # choose MutableSequence here. + class D(collections.defaultdict): + pass + + c.MutableSequence.register(D) + bases = [c.MutableSequence, c.MutableMapping] + for haystack in permutations(bases): + m = mro(D, haystack) + self.assertEqual( + m, + [ + D, + c.MutableSequence, + c.Sequence, + c.Reversible, + collections.defaultdict, + dict, + c.MutableMapping, + c.Mapping, + c.Collection, + c.Sized, + c.Iterable, + c.Container, + object, + ], + ) + + # Container and Callable are registered on different base classes and + # a generic function supporting both should always pick the Callable + # implementation if a C instance is passed. + class C(collections.defaultdict): + def __call__(self): + pass + + bases = [c.Sized, c.Callable, c.Container, c.Mapping] + for haystack in permutations(bases): + m = mro(C, haystack) + self.assertEqual( + m, + [ + C, + c.Callable, + collections.defaultdict, + dict, + c.Mapping, + c.Collection, + c.Sized, + c.Iterable, + c.Container, + object, + ], + ) + + def test_register_abc(self): + c = collections.abc + d = {"a": "b"} + l = [1, 2, 3] + s = {object(), None} + f = frozenset(s) + t = (1, 2, 3) + + @singledispatch + def g(obj): + return "base" + + self.assertEqual(g(d), "base") + self.assertEqual(g(l), "base") + self.assertEqual(g(s), "base") + self.assertEqual(g(f), "base") + self.assertEqual(g(t), "base") + g.register(c.Sized, lambda obj: "sized") + self.assertEqual(g(d), "sized") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(c.MutableMapping, lambda obj: "mutablemapping") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(collections.ChainMap, lambda obj: "chainmap") + self.assertEqual(g(d), "mutablemapping") # irrelevant ABCs registered + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(c.MutableSequence, lambda obj: "mutablesequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(c.MutableSet, lambda obj: "mutableset") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(c.Mapping, lambda obj: "mapping") + self.assertEqual(g(d), "mutablemapping") # not specific enough + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") + g.register(c.Sequence, lambda obj: "sequence") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sequence") + g.register(c.Set, lambda obj: "set") + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + g.register(dict, lambda obj: "dict") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "mutablesequence") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + g.register(list, lambda obj: "list") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "mutableset") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + g.register(set, lambda obj: "concrete-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "set") + self.assertEqual(g(t), "sequence") + g.register(frozenset, lambda obj: "frozen-set") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "sequence") + g.register(tuple, lambda obj: "tuple") + self.assertEqual(g(d), "dict") + self.assertEqual(g(l), "list") + self.assertEqual(g(s), "concrete-set") + self.assertEqual(g(f), "frozen-set") + self.assertEqual(g(t), "tuple") + + def test_c3_abc(self): + c = collections.abc + mro = functools._c3_mro + + class A(object): + pass + + class B(A): + def __len__(self): + return 0 # implies Sized + + @c.Container.register + class C(object): + pass + + class D(object): + pass # unrelated + + class X(D, C, B): + def __call__(self): + pass # implies Callable + + expected = [X, c.Callable, D, C, c.Container, B, c.Sized, A, object] + for abcs in permutations([c.Sized, c.Callable, c.Container]): + self.assertEqual(mro(X, abcs=abcs), expected) + # unrelated ABCs don't appear in the resulting MRO + many_abcs = [c.Mapping, c.Sized, c.Callable, c.Container, c.Iterable] + self.assertEqual(mro(X, abcs=many_abcs), expected) + + def test_mro_conflicts(self): + c = collections.abc + + @singledispatch + def g(arg): + return "base" + + class O(c.Sized): + def __len__(self): + return 0 + + o = O() + self.assertEqual(g(o), "base") + g.register(c.Iterable, lambda arg: "iterable") + g.register(c.Container, lambda arg: "container") + g.register(c.Sized, lambda arg: "sized") + g.register(c.Set, lambda arg: "set") + self.assertEqual(g(o), "sized") + c.Iterable.register(O) + self.assertEqual(g(o), "sized") # because it's explicitly in __mro__ + c.Container.register(O) + self.assertEqual(g(o), "sized") # see above: Sized is in __mro__ + c.Set.register(O) + self.assertEqual(g(o), "set") # because c.Set is a subclass of + + # c.Sized and c.Container + class P: + pass + + p = P() + self.assertEqual(g(p), "base") + c.Iterable.register(P) + self.assertEqual(g(p), "iterable") + c.Container.register(P) + with self.assertRaises(RuntimeError) as re_one: + g(p) + self.assertIn( + str(re_one.exception), + ( + ( + "Ambiguous dispatch: " + "or " + ), + ( + "Ambiguous dispatch: " + "or " + ), + ), + ) + + class Q(c.Sized): + def __len__(self): + return 0 + + q = Q() + self.assertEqual(g(q), "sized") + c.Iterable.register(Q) + self.assertEqual(g(q), "sized") # because it's explicitly in __mro__ + c.Set.register(Q) + self.assertEqual(g(q), "set") # because c.Set is a subclass of + + # c.Sized and c.Iterable + @singledispatch + def h(arg): + return "base" + + @h.register(c.Sized) + def _(arg): + return "sized" + + @h.register(c.Container) + def _(arg): + return "container" + + # Even though Sized and Container are explicit bases of MutableMapping, + # this ABC is implicitly registered on defaultdict which makes all of + # MutableMapping's bases implicit as well from defaultdict's + # perspective. + with self.assertRaises(RuntimeError) as re_two: + h(collections.defaultdict(lambda: 0)) + self.assertIn( + str(re_two.exception), + ( + ( + "Ambiguous dispatch: " + "or " + ), + ( + "Ambiguous dispatch: " + "or " + ), + ), + ) + + class R(collections.defaultdict): + pass + + c.MutableSequence.register(R) + + @singledispatch + def i(arg): + return "base" + + @i.register(c.MutableMapping) + def _(arg): + return "mapping" + + @i.register(c.MutableSequence) + def _(arg): + return "sequence" + + r = R() + self.assertEqual(i(r), "sequence") + class S: + pass -@some_fun.register(str) -def _some_fun_str(o: str) -> str: - return "It's a string!" + class T(S, c.Sized): + def __len__(self): + return 0 + t = T() + self.assertEqual(h(t), "sized") + c.Container.register(T) + self.assertEqual(h(t), "sized") # because it's explicitly in the MRO -@some_fun.register(int) -def _some_fun_int(o: int) -> str: - return "It's an int!" + class U: + def __len__(self): + return 0 + u = U() + self.assertEqual(h(u), "sized") # implicit Sized subclass inferred + # from the existence of __len__() + c.Container.register(U) + # There is no preference for registered versus inferred ABCs. + with self.assertRaises(RuntimeError) as re_three: + h(u) + self.assertIn( + str(re_three.exception), + ( + ( + "Ambiguous dispatch: " + "or " + ), + ( + "Ambiguous dispatch: " + "or " + ), + ), + ) -@some_fun.register(Sequence) -def _some_fun_sequence(l: Sequence) -> str: - return "Sequence: " + ", ".join(l) + class V(c.Sized, S): + def __len__(self): + return 0 + @singledispatch + def j(arg): + return "base" -@some_fun.register(tuple) -def _some_fun_tuple(l: tuple) -> str: - return "tuple: " + ", ".join(l) + @j.register(S) + def _(arg): + return "s" + @j.register(c.Container) + def _(arg): + return "container" -@pytest.mark.parametrize( - "v,ret", - [ - (None, "Got None "), - ("val", "It's a string!"), - (1, "It's an int!"), - (True, "It's an int!"), - ([], "Sequence: "), - (["1"], "Sequence: 1"), - (["1", "2", "3"], "Sequence: 1, 2, 3"), - (("1", "2", "3"), "tuple: 1, 2, 3"), - ] -) -def test_singledispatch(v, ret): - assert some_fun(v) == ret + v = V() + self.assertEqual(j(v), "s") + c.Container.register(V) + self.assertEqual(j(v), "container") # because it ends up right after + # Sized in the MRO diff --git a/uv.lock b/uv.lock index 3bec19c..cd48570 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,50 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version < '3.15'", +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] [[package]] name = "colorama" @@ -32,6 +76,91 @@ 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 = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/10/37fd9e9ba96cb0bd742dfb20fc3d082e54bdbec759d7300df927f360ef07/librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", size = 141706, upload-time = "2026-05-10T18:15:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/1b1466f358e4a0b728051f69bc27e67b432c6eaa2e05b88db49d3785ae0d/librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", size = 142605, upload-time = "2026-05-10T18:15:18.148Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/ed26dd2f6bc9a0baf48306433e579e8d354d70b2bcb78134ed950a5d0e1e/librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", size = 476555, upload-time = "2026-05-10T18:15:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/11891191c0e0a3fd617724e891f6e67a71a7658974a892b9a9a97fdb2977/librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", size = 468434, upload-time = "2026-05-10T18:15:20.87Z" }, + { url = "https://files.pythonhosted.org/packages/6f/50/5ec949d7f9ce1a07af903aa3e13abb98b717923bdead6e719b2f824ccc07/librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", size = 496918, upload-time = "2026-05-10T18:15:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c4/177336c7524e34875a38bf668e88b193a6723a4eb4045d07f74df6e1506c/librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", size = 490334, upload-time = "2026-05-10T18:15:24.2Z" }, + { url = "https://files.pythonhosted.org/packages/13/1f/da3112f7569eda3b49f9a2629bae1fe059812b6085df16c885f6454dff49/librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", size = 511287, upload-time = "2026-05-10T18:15:26.226Z" }, + { url = "https://files.pythonhosted.org/packages/fa/94/03fec301522e172d105581431223be56b27594ff46440ebfbb658a3735d5/librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", size = 517202, upload-time = "2026-05-10T18:15:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6e/339f6e5a7b413ce014f1917a756dae630fe59cc99f34153205b1cb540901/librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", size = 497517, upload-time = "2026-05-10T18:15:29.614Z" }, + { url = "https://files.pythonhosted.org/packages/cd/43/acdd5ce317cb46e8253ca9bfbdb8b12e68a24d745949336a7f3d5fb79ba0/librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", size = 538878, upload-time = "2026-05-10T18:15:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/29/b5/7a25bb12e3172839f647f196b3e988318b7bb1ca7501732a225c4dce2ec0/librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", size = 100070, upload-time = "2026-05-10T18:15:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0d/ebbcf4d77999c02c937b05d2b90ff4cd4dcc7e9a365ba132329ac1fe7a0f/librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", size = 117918, upload-time = "2026-05-10T18:15:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + [[package]] name = "maturin" version = "1.13.3" @@ -56,6 +185,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/93/e32e79333f0902ba292b996f504f5f06be59587f7d02ab8d5ed1e3066445/maturin-1.13.3-py3-none-win_arm64.whl", hash = "sha256:2389fe92d017cea9d94e521fa0175314a4c52f79a1057b901fbc9f8686ef7d0b", size = 9706562, upload-time = "2026-05-11T07:43:31.743Z" }, ] +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" }, + { url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" }, + { url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" }, + { url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -65,6 +262,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -109,6 +315,7 @@ source = { editable = "." } [package.dev-dependencies] dev = [ { name = "maturin" }, + { name = "mypy" }, { name = "pytest" }, ] @@ -117,6 +324,7 @@ dev = [ [package.metadata.requires-dev] dev = [ { name = "maturin" }, + { name = "mypy" }, { name = "pytest" }, ]