Skip to content

Commit dd0d5c5

Browse files
committed
Introduce PyTypeCheck::classinfo_object and make use of it
1 parent 2bb66c9 commit dd0d5c5

18 files changed

+220
-77
lines changed

newsfragments/5387.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `PyTypeCheck::classinfo_object` that returns an object that can be used as parameter in `isinstance` or `issubclass`

newsfragments/5387.changed.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Fetch type name dynamically on cast errors instead of using `PyTypeInfo::NAME`
1+
Fetch type name dynamically on cast errors instead of using `PyTypeCheck::NAME`

newsfragments/5387.removed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
removed `PyTypeCheck::NAME`

src/conversions/chrono.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -692,37 +692,37 @@ mod tests {
692692
let none = py.None().into_bound(py);
693693
assert_eq!(
694694
none.extract::<Duration>().unwrap_err().to_string(),
695-
"TypeError: 'NoneType' object cannot be converted to 'PyDelta'"
695+
"TypeError: 'NoneType' object cannot be converted to 'timedelta'"
696696
);
697697
assert_eq!(
698698
none.extract::<FixedOffset>().unwrap_err().to_string(),
699-
"TypeError: 'NoneType' object cannot be converted to 'PyTzInfo'"
699+
"TypeError: 'NoneType' object cannot be converted to 'tzinfo'"
700700
);
701701
assert_eq!(
702702
none.extract::<Utc>().unwrap_err().to_string(),
703703
"ValueError: expected datetime.timezone.utc"
704704
);
705705
assert_eq!(
706706
none.extract::<NaiveTime>().unwrap_err().to_string(),
707-
"TypeError: 'NoneType' object cannot be converted to 'PyTime'"
707+
"TypeError: 'NoneType' object cannot be converted to 'time'"
708708
);
709709
assert_eq!(
710710
none.extract::<NaiveDate>().unwrap_err().to_string(),
711-
"TypeError: 'NoneType' object cannot be converted to 'PyDate'"
711+
"TypeError: 'NoneType' object cannot be converted to 'date'"
712712
);
713713
assert_eq!(
714714
none.extract::<NaiveDateTime>().unwrap_err().to_string(),
715-
"TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
715+
"TypeError: 'NoneType' object cannot be converted to 'datetime'"
716716
);
717717
assert_eq!(
718718
none.extract::<DateTime<Utc>>().unwrap_err().to_string(),
719-
"TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
719+
"TypeError: 'NoneType' object cannot be converted to 'datetime'"
720720
);
721721
assert_eq!(
722722
none.extract::<DateTime<FixedOffset>>()
723723
.unwrap_err()
724724
.to_string(),
725-
"TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
725+
"TypeError: 'NoneType' object cannot be converted to 'datetime'"
726726
);
727727
});
728728
}

src/conversions/jiff.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -553,31 +553,31 @@ mod tests {
553553
let none = py.None().into_bound(py);
554554
assert_eq!(
555555
none.extract::<Span>().unwrap_err().to_string(),
556-
"TypeError: 'NoneType' object cannot be converted to 'PyDelta'"
556+
"TypeError: 'NoneType' object cannot be converted to 'timedelta'"
557557
);
558558
assert_eq!(
559559
none.extract::<Offset>().unwrap_err().to_string(),
560-
"TypeError: 'NoneType' object cannot be converted to 'PyTzInfo'"
560+
"TypeError: 'NoneType' object cannot be converted to 'tzinfo'"
561561
);
562562
assert_eq!(
563563
none.extract::<TimeZone>().unwrap_err().to_string(),
564-
"TypeError: 'NoneType' object cannot be converted to 'PyTzInfo'"
564+
"TypeError: 'NoneType' object cannot be converted to 'tzinfo'"
565565
);
566566
assert_eq!(
567567
none.extract::<Time>().unwrap_err().to_string(),
568-
"TypeError: 'NoneType' object cannot be converted to 'PyTime'"
568+
"TypeError: 'NoneType' object cannot be converted to 'time'"
569569
);
570570
assert_eq!(
571571
none.extract::<Date>().unwrap_err().to_string(),
572-
"TypeError: 'NoneType' object cannot be converted to 'PyDate'"
572+
"TypeError: 'NoneType' object cannot be converted to 'date'"
573573
);
574574
assert_eq!(
575575
none.extract::<DateTime>().unwrap_err().to_string(),
576-
"TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
576+
"TypeError: 'NoneType' object cannot be converted to 'datetime'"
577577
);
578578
assert_eq!(
579579
none.extract::<Zoned>().unwrap_err().to_string(),
580-
"TypeError: 'NoneType' object cannot be converted to 'PyDateTime'"
580+
"TypeError: 'NoneType' object cannot be converted to 'datetime'"
581581
);
582582
});
583583
}

src/conversions/smallvec.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use crate::inspect::types::TypeInfo;
2222
use crate::types::any::PyAnyMethods;
2323
use crate::types::{PySequence, PyString};
2424
use crate::{
25-
err::DowncastError, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python,
25+
err::DowncastError, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo,
26+
Python,
2627
};
2728
use smallvec::{Array, SmallVec};
2829

@@ -102,7 +103,11 @@ where
102103
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
103104
obj.cast_unchecked::<PySequence>()
104105
} else {
105-
return Err(DowncastError::new_from_borrowed(obj, "Sequence").into());
106+
return Err(DowncastError::new_from_type(
107+
obj,
108+
PySequence::type_object(obj.py()).into_any(),
109+
)
110+
.into());
106111
}
107112
};
108113

src/conversions/std/array.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::conversion::{FromPyObjectOwned, IntoPyObject};
22
use crate::types::any::PyAnyMethods;
33
use crate::types::PySequence;
4-
use crate::{err::DowncastError, ffi, FromPyObject, PyAny, PyResult, Python};
4+
use crate::{err::DowncastError, ffi, FromPyObject, PyAny, PyResult, PyTypeInfo, Python};
55
use crate::{exceptions, Borrowed, Bound, PyErr};
66

77
impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N]
@@ -57,7 +57,11 @@ where
5757
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
5858
obj.cast_unchecked::<PySequence>()
5959
} else {
60-
return Err(DowncastError::new_from_borrowed(obj, "Sequence").into());
60+
return Err(DowncastError::new_from_type(
61+
obj,
62+
PySequence::type_object(obj.py()).into_any(),
63+
)
64+
.into());
6165
}
6266
};
6367
let seq_len = seq.len()?;

src/err/mod.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::type_object::PyTypeInfo;
44
use crate::types::any::PyAnyMethods;
55
use crate::types::{
66
string::PyStringMethods, traceback::PyTracebackMethods, typeobject::PyTypeMethods, PyTraceback,
7-
PyType,
7+
PyTuple, PyTupleMethods, PyType,
88
};
99
use crate::{
1010
exceptions::{self, PyBaseException},
@@ -59,17 +59,7 @@ impl<'a, 'py> DowncastError<'a, 'py> {
5959
}
6060
}
6161

62-
pub(crate) fn new_from_borrowed(
63-
from: Borrowed<'a, 'py, PyAny>,
64-
to: impl Into<Cow<'static, str>>,
65-
) -> Self {
66-
Self {
67-
from,
68-
to: TypeNameOrValue::Name(to.into()),
69-
}
70-
}
71-
72-
pub(crate) fn new_from_type(from: Borrowed<'a, 'py, PyAny>, to: Bound<'py, PyType>) -> Self {
62+
pub(crate) fn new_from_type(from: Borrowed<'a, 'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
7363
Self {
7464
from,
7565
to: TypeNameOrValue::Value(to),
@@ -94,7 +84,7 @@ impl<'py> DowncastIntoError<'py> {
9484
}
9585
}
9686

97-
pub(crate) fn new_from_type(from: Bound<'py, PyAny>, to: Bound<'py, PyType>) -> Self {
87+
pub(crate) fn new_from_type(from: Bound<'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
9888
Self {
9989
from,
10090
to: TypeNameOrValue::Value(to),
@@ -114,7 +104,7 @@ impl<'py> DowncastIntoError<'py> {
114104
#[derive(Debug)]
115105
enum TypeNameOrValue<'py> {
116106
Name(Cow<'static, str>),
117-
Value(Bound<'py, PyType>),
107+
Value(Bound<'py, PyAny>),
118108
}
119109
/// Helper conversion trait that allows to use custom arguments for lazy exception construction.
120110
pub trait PyErrArguments: Send + Sync {
@@ -766,7 +756,7 @@ impl PyErrArguments for PyDowncastErrorArguments {
766756

767757
enum OwnedTypeNameOrValue {
768758
Name(Cow<'static, str>),
769-
Value(Py<PyType>),
759+
Value(Py<PyAny>),
770760
}
771761

772762
/// Python exceptions that can be converted to [`PyErr`].
@@ -850,11 +840,24 @@ impl std::fmt::Display for TypeNameOrValue<'_> {
850840
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
851841
match self {
852842
Self::Name(name) => name.fmt(f),
853-
Self::Value(t) => t
854-
.qualname()
855-
.map_err(|_| std::fmt::Error)?
856-
.to_string_lossy()
857-
.fmt(f),
843+
Self::Value(t) => {
844+
if let Ok(t) = t.downcast::<PyType>() {
845+
t.qualname()
846+
.map_err(|_| std::fmt::Error)?
847+
.to_string_lossy()
848+
.fmt(f)
849+
} else if let Ok(t) = t.downcast::<PyTuple>() {
850+
for (i, t) in t.iter().enumerate() {
851+
if i > 0 {
852+
f.write_str(" | ")?;
853+
}
854+
TypeNameOrValue::Value(t).fmt(f)?;
855+
}
856+
Ok(())
857+
} else {
858+
t.fmt(f)
859+
}
860+
}
858861
}
859862
}
860863
}

src/instance.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ impl<'py, T> Bound<'py, T> {
167167
// Safety: type_check is responsible for ensuring that the type is correct
168168
Ok(unsafe { any.cast_unchecked() })
169169
} else {
170-
Err(DowncastError::new(any, U::NAME))
170+
Err(DowncastError::new_from_type(
171+
any.as_borrowed(),
172+
U::classinfo_object(any.py()),
173+
))
171174
}
172175
}
173176

@@ -211,7 +214,8 @@ impl<'py, T> Bound<'py, T> {
211214
// Safety: type_check is responsible for ensuring that the type is correct
212215
Ok(unsafe { any.cast_into_unchecked() })
213216
} else {
214-
Err(DowncastIntoError::new(any, U::NAME))
217+
let to = U::classinfo_object(any.py());
218+
Err(DowncastIntoError::new_from_type(any, to))
215219
}
216220
}
217221

@@ -266,7 +270,7 @@ impl<'py, T> Bound<'py, T> {
266270
} else {
267271
Err(DowncastError::new_from_type(
268272
any.as_borrowed(),
269-
U::type_object(any.py()),
273+
U::type_object(any.py()).into_any(),
270274
))
271275
}
272276
}
@@ -289,7 +293,7 @@ impl<'py, T> Bound<'py, T> {
289293
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
290294
Ok(unsafe { any.cast_into_unchecked() })
291295
} else {
292-
let to = U::type_object(any.py());
296+
let to = U::type_object(any.py()).into_any();
293297
Err(DowncastIntoError::new_from_type(any, to))
294298
}
295299
}
@@ -1048,7 +1052,10 @@ impl<'a, 'py> Borrowed<'a, 'py, PyAny> {
10481052
// Safety: type_check is responsible for ensuring that the type is correct
10491053
Ok(unsafe { self.cast_unchecked() })
10501054
} else {
1051-
Err(DowncastError::new_from_borrowed(self, T::NAME))
1055+
Err(DowncastError::new_from_type(
1056+
self,
1057+
T::classinfo_object(self.py()),
1058+
))
10521059
}
10531060
}
10541061

src/pybacked.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::{convert::Infallible, ops::Deref, ptr::NonNull, sync::Arc};
55
use crate::{
66
types::{
77
bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
8-
PyBytes, PyString,
8+
PyBytes, PyString, PyTuple,
99
},
10-
Borrowed, Bound, DowncastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
10+
Borrowed, Bound, DowncastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyTypeInfo,
11+
Python,
1112
};
1213

1314
/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
@@ -212,9 +213,17 @@ impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes {
212213
} else if let Ok(bytearray) = obj.cast::<PyByteArray>() {
213214
Ok(Self::from(bytearray.to_owned()))
214215
} else {
215-
Err(DowncastError::new_from_borrowed(
216+
Err(DowncastError::new_from_type(
216217
obj,
217-
"`bytes` or `bytearray`",
218+
PyTuple::new(
219+
obj.py(),
220+
[
221+
PyBytes::type_object(obj.py()),
222+
PyByteArray::type_object(obj.py()),
223+
],
224+
)
225+
.unwrap()
226+
.into_any(),
218227
))
219228
}
220229
}

0 commit comments

Comments
 (0)