Skip to content

Commit 72c561c

Browse files
Merge #2882
2882: inspect: gate behind `experimental-inspect` feature r=davidhewitt a=davidhewitt This is the last thing I want to do before preparing 0.18 release. The `pyo3::inspect` functionality looks useful as a first step towards #2454. However, we don't actually make use of this anywhere within PyO3 yet (we could probably use it for better error messages). I think we also have open questions about the traits which I'd like to resolve before committing to these additional APIs. (For example, this PR adds `IntoPy::type_output`, which seems potentially misplaced to me, the `type_output` function probably wants to be on a non-generic trait e.g. `ToPyObject` or maybe #2316.) As such, I propose putting these APIs behind an `experimental-inspect` feature gate for now, and invite users who find them useful to contribute a finished-off design. Co-authored-by: David Hewitt <[email protected]>
2 parents 713f51a + 20ca3be commit 72c561c

15 files changed

+81
-13
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ pyo3-build-config = { path = "pyo3-build-config", version = "0.17.3", features =
6161
[features]
6262
default = ["macros"]
6363

64+
# Enables pyo3::inspect module and additional type information on FromPyObject
65+
# and IntoPy traits
66+
experimental-inspect = []
67+
6468
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
6569
macros = ["pyo3-macros", "indoc", "unindent"]
6670

@@ -105,6 +109,7 @@ full = [
105109
"indexmap",
106110
"eyre",
107111
"anyhow",
112+
"experimental-inspect",
108113
]
109114

110115
[[bench]]

guide/src/features.md

+6
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ If you do not enable this feature, you should call `pyo3::prepare_freethreaded_p
5151

5252
## Advanced Features
5353

54+
### `experimental-inspect`
55+
56+
This feature adds the `pyo3::inspect` module, as well as `IntoPy::type_output` and `FromPyObject::type_input` APIs to produce Python type "annotations" for Rust types.
57+
58+
This is a first step towards adding first-class support for generating type annotations automatically in PyO3, however work is needed to finish this off. All feedback and offers of help welcome on [issue #2454](https://github.com/PyO3/pyo3/issues/2454).
59+
5460
### `macros`
5561

5662
This feature enables a dependency on the `pyo3-macros` crate, which provides the procedural macros portion of PyO3's API:

guide/src/python_typing_hints.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ PyO3 provides an easy to use interface to code native Python libraries in Rust.
44

55
Currently the best solution for the problem is to manually maintain `*.pyi` files and ship them along with the package.
66

7+
There is a sketch of a roadmap towards completing [the `experimental-inspect` feature](./features.md#experimental-inspect) which may eventually lead to automatic type annotations generated by PyO3. This needs more testing and implementation, please see [issue #2454](https://github.com/PyO3/pyo3/issues/2454).
8+
79
## Introduction to `pyi` files
810

911
`pyi` files (an abbreviation for `Python Interface`) are called "stub files" in most of the documentation related to them. A very good definition of what it is can be found in [old MyPy documentation](https://github.com/python/mypy/wiki/Creating-Stubs-For-Python-Modules):

src/conversion.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
//! Defines conversions between Rust and Python types.
44
use crate::err::{self, PyDowncastError, PyResult};
5+
#[cfg(feature = "experimental-inspect")]
56
use crate::inspect::types::TypeInfo;
67
use crate::pyclass::boolean_struct::False;
78
use crate::type_object::PyTypeInfo;
@@ -253,6 +254,7 @@ pub trait IntoPy<T>: Sized {
253254
///
254255
/// For most types, the return value for this method will be identical to that of [`FromPyObject::type_input`].
255256
/// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument.
257+
#[cfg(feature = "experimental-inspect")]
256258
fn type_output() -> TypeInfo {
257259
TypeInfo::Any
258260
}
@@ -309,6 +311,7 @@ pub trait FromPyObject<'source>: Sized {
309311
///
310312
/// For most types, the return value for this method will be identical to that of [`IntoPy::type_output`].
311313
/// It may be different for some types, such as `Dict`, to allow duck-typing: functions return `Dict` but take `Mapping` as argument.
314+
#[cfg(feature = "experimental-inspect")]
312315
fn type_input() -> TypeInfo {
313316
TypeInfo::Any
314317
}

src/conversions/std/map.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::{cmp, collections, hash};
22

3+
#[cfg(feature = "experimental-inspect")]
4+
use crate::inspect::types::TypeInfo;
35
use crate::{
4-
inspect::types::TypeInfo,
56
types::{IntoPyDict, PyDict},
67
FromPyObject, IntoPy, PyAny, PyErr, PyObject, Python, ToPyObject,
78
};
@@ -40,6 +41,7 @@ where
4041
IntoPyDict::into_py_dict(iter, py).into()
4142
}
4243

44+
#[cfg(feature = "experimental-inspect")]
4345
fn type_output() -> TypeInfo {
4446
TypeInfo::dict_of(K::type_output(), V::type_output())
4547
}
@@ -57,6 +59,7 @@ where
5759
IntoPyDict::into_py_dict(iter, py).into()
5860
}
5961

62+
#[cfg(feature = "experimental-inspect")]
6063
fn type_output() -> TypeInfo {
6164
TypeInfo::dict_of(K::type_output(), V::type_output())
6265
}
@@ -77,6 +80,7 @@ where
7780
Ok(ret)
7881
}
7982

83+
#[cfg(feature = "experimental-inspect")]
8084
fn type_input() -> TypeInfo {
8185
TypeInfo::mapping_of(K::type_input(), V::type_input())
8286
}
@@ -96,6 +100,7 @@ where
96100
Ok(ret)
97101
}
98102

103+
#[cfg(feature = "experimental-inspect")]
99104
fn type_input() -> TypeInfo {
100105
TypeInfo::mapping_of(K::type_input(), V::type_input())
101106
}

src/conversions/std/num.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#[cfg(feature = "experimental-inspect")]
2+
use crate::inspect::types::TypeInfo;
13
use crate::{
2-
exceptions, ffi, inspect::types::TypeInfo, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr,
3-
PyObject, PyResult, Python, ToPyObject,
4+
exceptions, ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python,
5+
ToPyObject,
46
};
57
use std::convert::TryFrom;
68
use std::num::{
@@ -22,6 +24,7 @@ macro_rules! int_fits_larger_int {
2224
(self as $larger_type).into_py(py)
2325
}
2426

27+
#[cfg(feature = "experimental-inspect")]
2528
fn type_output() -> TypeInfo {
2629
<$larger_type>::type_output()
2730
}
@@ -34,6 +37,7 @@ macro_rules! int_fits_larger_int {
3437
.map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
3538
}
3639

40+
#[cfg(feature = "experimental-inspect")]
3741
fn type_input() -> TypeInfo {
3842
<$larger_type>::type_input()
3943
}
@@ -55,6 +59,7 @@ macro_rules! int_convert_u64_or_i64 {
5559
unsafe { PyObject::from_owned_ptr(py, $pylong_from_ll_or_ull(self)) }
5660
}
5761

62+
#[cfg(feature = "experimental-inspect")]
5863
fn type_output() -> TypeInfo {
5964
TypeInfo::builtin("int")
6065
}
@@ -74,6 +79,7 @@ macro_rules! int_convert_u64_or_i64 {
7479
}
7580
}
7681

82+
#[cfg(feature = "experimental-inspect")]
7783
fn type_input() -> TypeInfo {
7884
Self::type_output()
7985
}
@@ -93,6 +99,7 @@ macro_rules! int_fits_c_long {
9399
unsafe { PyObject::from_owned_ptr(py, ffi::PyLong_FromLong(self as c_long)) }
94100
}
95101

102+
#[cfg(feature = "experimental-inspect")]
96103
fn type_output() -> TypeInfo {
97104
TypeInfo::builtin("int")
98105
}
@@ -115,6 +122,7 @@ macro_rules! int_fits_c_long {
115122
.map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
116123
}
117124

125+
#[cfg(feature = "experimental-inspect")]
118126
fn type_input() -> TypeInfo {
119127
Self::type_output()
120128
}
@@ -183,6 +191,7 @@ mod fast_128bit_int_conversion {
183191
}
184192
}
185193

194+
#[cfg(feature = "experimental-inspect")]
186195
fn type_output() -> TypeInfo {
187196
TypeInfo::builtin("int")
188197
}
@@ -209,6 +218,7 @@ mod fast_128bit_int_conversion {
209218
}
210219
}
211220

221+
#[cfg(feature = "experimental-inspect")]
212222
fn type_input() -> TypeInfo {
213223
Self::type_output()
214224
}
@@ -255,6 +265,7 @@ mod slow_128bit_int_conversion {
255265
}
256266
}
257267

268+
#[cfg(feature = "experimental-inspect")]
258269
fn type_output() -> TypeInfo {
259270
TypeInfo::builtin("int")
260271
}
@@ -278,6 +289,7 @@ mod slow_128bit_int_conversion {
278289
}
279290
}
280291

292+
#[cfg(feature = "experimental-inspect")]
281293
fn type_input() -> TypeInfo {
282294
Self::type_output()
283295
}
@@ -324,6 +336,7 @@ macro_rules! nonzero_int_impl {
324336
.map_err(|_| exceptions::PyValueError::new_err("invalid zero value"))
325337
}
326338

339+
#[cfg(feature = "experimental-inspect")]
327340
fn type_input() -> TypeInfo {
328341
<$primitive_type>::type_input()
329342
}

src/conversions/std/set.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::{cmp, collections, hash};
22

3+
#[cfg(feature = "experimental-inspect")]
4+
use crate::inspect::types::TypeInfo;
35
use crate::{
4-
inspect::types::TypeInfo, types::set::new_from_iter, types::PySet, FromPyObject, IntoPy, PyAny,
5-
PyObject, PyResult, Python, ToPyObject,
6+
types::set::new_from_iter, types::PySet, FromPyObject, IntoPy, PyAny, PyObject, PyResult,
7+
Python, ToPyObject,
68
};
79

810
impl<T, S> ToPyObject for collections::HashSet<T, S>
@@ -39,6 +41,7 @@ where
3941
.into()
4042
}
4143

44+
#[cfg(feature = "experimental-inspect")]
4245
fn type_output() -> TypeInfo {
4346
TypeInfo::set_of(K::type_output())
4447
}
@@ -54,6 +57,7 @@ where
5457
set.iter().map(K::extract).collect()
5558
}
5659

60+
#[cfg(feature = "experimental-inspect")]
5761
fn type_input() -> TypeInfo {
5862
TypeInfo::set_of(K::type_input())
5963
}
@@ -69,6 +73,7 @@ where
6973
.into()
7074
}
7175

76+
#[cfg(feature = "experimental-inspect")]
7277
fn type_output() -> TypeInfo {
7378
TypeInfo::set_of(K::type_output())
7479
}
@@ -83,6 +88,7 @@ where
8388
set.iter().map(K::extract).collect()
8489
}
8590

91+
#[cfg(feature = "experimental-inspect")]
8692
fn type_input() -> TypeInfo {
8793
TypeInfo::set_of(K::type_input())
8894
}

src/conversions/std/slice.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
use crate::{
2-
inspect::types::TypeInfo, types::PyBytes, FromPyObject, IntoPy, PyAny, PyObject, PyResult,
3-
Python, ToPyObject,
4-
};
1+
#[cfg(feature = "experimental-inspect")]
2+
use crate::inspect::types::TypeInfo;
3+
use crate::{types::PyBytes, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject};
54

65
impl<'a> IntoPy<PyObject> for &'a [u8] {
76
fn into_py(self, py: Python<'_>) -> PyObject {
87
PyBytes::new(py, self).to_object(py)
98
}
109

10+
#[cfg(feature = "experimental-inspect")]
1111
fn type_output() -> TypeInfo {
1212
TypeInfo::builtin("bytes")
1313
}
@@ -18,6 +18,7 @@ impl<'a> FromPyObject<'a> for &'a [u8] {
1818
Ok(obj.downcast::<PyBytes>()?.as_bytes())
1919
}
2020

21+
#[cfg(feature = "experimental-inspect")]
2122
fn type_input() -> TypeInfo {
2223
Self::type_output()
2324
}

src/conversions/std/string.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::borrow::Cow;
22

3+
#[cfg(feature = "experimental-inspect")]
4+
use crate::inspect::types::TypeInfo;
35
use crate::{
4-
inspect::types::TypeInfo, types::PyString, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult,
5-
Python, ToPyObject,
6+
types::PyString, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject,
67
};
78

89
/// Converts a Rust `str` to a Python object.
@@ -20,6 +21,7 @@ impl<'a> IntoPy<PyObject> for &'a str {
2021
PyString::new(py, self).into()
2122
}
2223

24+
#[cfg(feature = "experimental-inspect")]
2325
fn type_output() -> TypeInfo {
2426
<String>::type_output()
2527
}
@@ -31,6 +33,7 @@ impl<'a> IntoPy<Py<PyString>> for &'a str {
3133
PyString::new(py, self).into()
3234
}
3335

36+
#[cfg(feature = "experimental-inspect")]
3437
fn type_output() -> TypeInfo {
3538
<String>::type_output()
3639
}
@@ -51,6 +54,7 @@ impl IntoPy<PyObject> for Cow<'_, str> {
5154
self.to_object(py)
5255
}
5356

57+
#[cfg(feature = "experimental-inspect")]
5458
fn type_output() -> TypeInfo {
5559
<String>::type_output()
5660
}
@@ -77,6 +81,7 @@ impl IntoPy<PyObject> for char {
7781
PyString::new(py, self.encode_utf8(&mut bytes)).into()
7882
}
7983

84+
#[cfg(feature = "experimental-inspect")]
8085
fn type_output() -> TypeInfo {
8186
<String>::type_output()
8287
}
@@ -87,6 +92,7 @@ impl IntoPy<PyObject> for String {
8792
PyString::new(py, &self).into()
8893
}
8994

95+
#[cfg(feature = "experimental-inspect")]
9096
fn type_output() -> TypeInfo {
9197
TypeInfo::builtin("str")
9298
}
@@ -98,6 +104,7 @@ impl<'a> IntoPy<PyObject> for &'a String {
98104
PyString::new(py, self).into()
99105
}
100106

107+
#[cfg(feature = "experimental-inspect")]
101108
fn type_output() -> TypeInfo {
102109
<String>::type_output()
103110
}
@@ -110,6 +117,7 @@ impl<'source> FromPyObject<'source> for &'source str {
110117
ob.downcast::<PyString>()?.to_str()
111118
}
112119

120+
#[cfg(feature = "experimental-inspect")]
113121
fn type_input() -> TypeInfo {
114122
<String>::type_input()
115123
}
@@ -122,6 +130,7 @@ impl FromPyObject<'_> for String {
122130
obj.downcast::<PyString>()?.to_str().map(ToOwned::to_owned)
123131
}
124132

133+
#[cfg(feature = "experimental-inspect")]
125134
fn type_input() -> TypeInfo {
126135
Self::type_output()
127136
}
@@ -140,6 +149,7 @@ impl FromPyObject<'_> for char {
140149
}
141150
}
142151

152+
#[cfg(feature = "experimental-inspect")]
143153
fn type_input() -> TypeInfo {
144154
<String>::type_input()
145155
}

src/conversions/std/vec.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#[cfg(feature = "experimental-inspect")]
12
use crate::inspect::types::TypeInfo;
23
use crate::types::list::new_from_iter;
34
use crate::{IntoPy, PyObject, Python, ToPyObject};
@@ -32,6 +33,7 @@ where
3233
list.into()
3334
}
3435

36+
#[cfg(feature = "experimental-inspect")]
3537
fn type_output() -> TypeInfo {
3638
TypeInfo::list_of(T::type_output())
3739
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ mod macros;
445445
#[cfg(all(test, feature = "macros"))]
446446
mod test_hygiene;
447447

448+
#[cfg(feature = "experimental-inspect")]
448449
pub mod inspect;
449450

450451
/// Test readme and user guide

0 commit comments

Comments
 (0)