Skip to content

Commit 8c13f76

Browse files
committed
Merge branch 'main' into either-type
2 parents 18f84e1 + 61bc02d commit 8c13f76

File tree

134 files changed

+2141
-1121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+2141
-1121
lines changed

.github/workflows/benches.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ on:
99
# performance analysis in order to generate initial data.
1010
workflow_dispatch:
1111

12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}-benches
14+
cancel-in-progress: true
15+
1216
jobs:
1317
benchmarks:
1418
runs-on: ubuntu-latest

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,18 @@ jobs:
468468
echo PYO3_CONFIG_FILE=$PYO3_CONFIG_FILE >> $GITHUB_ENV
469469
- run: python3 -m nox -s test
470470

471+
test-version-limits:
472+
needs: [fmt]
473+
if: ${{ contains(github.event.pull_request.labels.*.name, 'CI-build-full') || (github.event_name != 'pull_request' && github.ref != 'refs/heads/main') }}
474+
runs-on: ubuntu-latest
475+
steps:
476+
- uses: actions/checkout@v4
477+
- uses: Swatinem/rust-cache@v2
478+
continue-on-error: true
479+
- uses: dtolnay/rust-toolchain@stable
480+
- run: python3 -m pip install --upgrade pip && pip install nox
481+
- run: python3 -m nox -s test-version-limits
482+
471483
conclusion:
472484
needs:
473485
- fmt
@@ -480,6 +492,8 @@ jobs:
480492
- docsrs
481493
- coverage
482494
- emscripten
495+
- test-debug
496+
- test-version-limits
483497
if: always()
484498
runs-on: ubuntu-latest
485499
steps:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
8181
extension-module = ["pyo3-ffi/extension-module"]
8282

8383
# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for more.
84-
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3", "pyo3-macros/abi3"]
84+
abi3 = ["pyo3-build-config/abi3", "pyo3-ffi/abi3"]
8585

8686
# With abi3, we can manually set the minimum Python version.
8787
abi3-py37 = ["abi3-py38", "pyo3-build-config/abi3-py37", "pyo3-ffi/abi3-py37"]

Contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Helping others often reveals bugs, documentation weaknesses, and missing APIs. I
3535

3636
### Implement issues ready for development
3737

38-
Issues where the solution is clear and work is not in progress use the [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implemeter) label.
38+
Issues where the solution is clear and work is not in progress use the [needs-implementer](https://github.com/PyO3/pyo3/issues?q=is%3Aissue+is%3Aopen+label%3Aneeds-implementer) label.
3939

4040
Don't be afraid if the solution is not clear to you! The core PyO3 contributors will be happy to mentor you through any questions you have to help you write the solution.
4141

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ use pyo3::types::IntoPyDict;
149149

150150
fn main() -> PyResult<()> {
151151
Python::with_gil(|py| {
152-
let sys = py.import("sys")?;
152+
let sys = py.import_bound("sys")?;
153153
let version: String = sys.getattr("version")?.extract()?;
154154

155-
let locals = [("os", py.import("os")?)].into_py_dict_bound(py);
155+
let locals = [("os", py.import_bound("os")?)].into_py_dict_bound(py);
156156
let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
157157
let user: String = py.eval_bound(code, None, Some(&locals))?.extract()?;
158158

guide/src/class.md

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ struct MyClass {
216216
num: i32,
217217
}
218218
Python::with_gil(|py| {
219+
# #[allow(deprecated)]
219220
let obj = PyCell::new(py, MyClass { num: 3 }).unwrap();
220221
{
221222
let obj_ref = obj.borrow(); // Get PyRef
@@ -397,11 +398,11 @@ impl SubSubClass {
397398
}
398399
}
399400
# Python::with_gil(|py| {
400-
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
401+
# let subsub = pyo3::Py::new(py, SubSubClass::new()).unwrap();
401402
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000");
402403
# let subsub = SubSubClass::factory_method(py, 2).unwrap();
403404
# let subsubsub = SubSubClass::factory_method(py, 3).unwrap();
404-
# let cls = py.get_type::<SubSubClass>();
405+
# let cls = py.get_type_bound::<SubSubClass>();
405406
# pyo3::py_run!(py, subsub cls, "assert not isinstance(subsub, cls)");
406407
# pyo3::py_run!(py, subsubsub cls, "assert isinstance(subsubsub, cls)");
407408
# });
@@ -441,7 +442,7 @@ impl DictWithCounter {
441442
}
442443
}
443444
# Python::with_gil(|py| {
444-
# let cnt = pyo3::PyCell::new(py, DictWithCounter::new()).unwrap();
445+
# let cnt = pyo3::Py::new(py, DictWithCounter::new()).unwrap();
445446
# pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10")
446447
# });
447448
# }
@@ -497,7 +498,7 @@ impl MyDict {
497498
// some custom methods that use `private` here...
498499
}
499500
# Python::with_gil(|py| {
500-
# let cls = py.get_type::<MyDict>();
501+
# let cls = py.get_type_bound::<MyDict>();
501502
# pyo3::py_run!(py, cls, "cls(a=1, b=2)")
502503
# });
503504
# }
@@ -691,7 +692,7 @@ This is the equivalent of the Python decorator `@classmethod`.
691692
#[pymethods]
692693
impl MyClass {
693694
#[classmethod]
694-
fn cls_method(cls: &PyType) -> PyResult<i32> {
695+
fn cls_method(cls: &Bound<'_, PyType>) -> PyResult<i32> {
695696
Ok(10)
696697
}
697698
}
@@ -719,10 +720,10 @@ To create a constructor which takes a positional class argument, you can combine
719720
impl BaseClass {
720721
#[new]
721722
#[classmethod]
722-
fn py_new<'p>(cls: &'p PyType, py: Python<'p>) -> PyResult<Self> {
723+
fn py_new(cls: &Bound<'_, PyType>) -> PyResult<Self> {
723724
// Get an abstract attribute (presumably) declared on a subclass of this class.
724-
let subclass_attr = cls.getattr("a_class_attr")?;
725-
Ok(Self(subclass_attr.to_object(py)))
725+
let subclass_attr: Bound<'_, PyAny> = cls.getattr("a_class_attr")?;
726+
Ok(Self(subclass_attr.unbind()))
726727
}
727728
}
728729
```
@@ -767,7 +768,7 @@ impl MyClass {
767768
}
768769

769770
Python::with_gil(|py| {
770-
let my_class = py.get_type::<MyClass>();
771+
let my_class = py.get_type_bound::<MyClass>();
771772
pyo3::py_run!(py, my_class, "assert my_class.my_attribute == 'hello'")
772773
});
773774
```
@@ -928,7 +929,7 @@ impl MyClass {
928929
// similarly for classmethod arguments, use $cls
929930
#[classmethod]
930931
#[pyo3(text_signature = "($cls, e, f)")]
931-
fn my_class_method(cls: &PyType, e: i32, f: i32) -> i32 {
932+
fn my_class_method(cls: &Bound<'_, PyType>, e: i32, f: i32) -> i32 {
932933
e + f
933934
}
934935
#[staticmethod]
@@ -1026,7 +1027,7 @@ enum MyEnum {
10261027
Python::with_gil(|py| {
10271028
let x = Py::new(py, MyEnum::Variant).unwrap();
10281029
let y = Py::new(py, MyEnum::OtherVariant).unwrap();
1029-
let cls = py.get_type::<MyEnum>();
1030+
let cls = py.get_type_bound::<MyEnum>();
10301031
pyo3::py_run!(py, x y cls, r#"
10311032
assert x == cls.Variant
10321033
assert y == cls.OtherVariant
@@ -1046,7 +1047,7 @@ enum MyEnum {
10461047
}
10471048

10481049
Python::with_gil(|py| {
1049-
let cls = py.get_type::<MyEnum>();
1050+
let cls = py.get_type_bound::<MyEnum>();
10501051
let x = MyEnum::Variant as i32; // The exact value is assigned by the compiler.
10511052
pyo3::py_run!(py, cls x, r#"
10521053
assert int(cls.Variant) == x
@@ -1068,7 +1069,7 @@ enum MyEnum{
10681069
}
10691070

10701071
Python::with_gil(|py| {
1071-
let cls = py.get_type::<MyEnum>();
1072+
let cls = py.get_type_bound::<MyEnum>();
10721073
let x = Py::new(py, MyEnum::Variant).unwrap();
10731074
pyo3::py_run!(py, cls x, r#"
10741075
assert repr(x) == 'MyEnum.Variant'
@@ -1094,7 +1095,7 @@ impl MyEnum {
10941095
}
10951096

10961097
Python::with_gil(|py| {
1097-
let cls = py.get_type::<MyEnum>();
1098+
let cls = py.get_type_bound::<MyEnum>();
10981099
pyo3::py_run!(py, cls, "assert repr(cls.Answer) == '42'")
10991100
})
11001101
```
@@ -1111,7 +1112,7 @@ enum MyEnum {
11111112

11121113
Python::with_gil(|py| {
11131114
let x = Py::new(py, MyEnum::Variant).unwrap();
1114-
let cls = py.get_type::<MyEnum>();
1115+
let cls = py.get_type_bound::<MyEnum>();
11151116
pyo3::py_run!(py, x cls, r#"
11161117
assert repr(x) == 'RenamedEnum.UPPERCASE'
11171118
assert x == cls.UPPERCASE
@@ -1165,7 +1166,7 @@ enum Shape {
11651166
Python::with_gil(|py| {
11661167
let circle = Shape::Circle { radius: 10.0 }.into_py(py);
11671168
let square = Shape::RegularPolygon { side_count: 4, radius: 10.0 }.into_py(py);
1168-
let cls = py.get_type::<Shape>();
1169+
let cls = py.get_type_bound::<Shape>();
11691170
pyo3::py_run!(py, circle square cls, r#"
11701171
assert isinstance(circle, cls)
11711172
assert isinstance(circle, cls.Circle)
@@ -1204,7 +1205,7 @@ enum MyEnum {
12041205

12051206
Python::with_gil(|py| {
12061207
let x = Py::new(py, MyEnum::Variant { i: 42 }).unwrap();
1207-
let cls = py.get_type::<MyEnum>();
1208+
let cls = py.get_type_bound::<MyEnum>();
12081209
pyo3::py_run!(py, x cls, r#"
12091210
assert isinstance(x, cls)
12101211
assert not isinstance(x, cls.Variant)
@@ -1308,7 +1309,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
13081309
}
13091310

13101311
# Python::with_gil(|py| {
1311-
# let cls = py.get_type::<MyClass>();
1312+
# let cls = py.get_type_bound::<MyClass>();
13121313
# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'")
13131314
# });
13141315
# }

guide/src/class/numeric.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ assert hash_djb2('l50_50') == Number(-1152549421)
206206
```rust
207207
use std::collections::hash_map::DefaultHasher;
208208
use std::hash::{Hash, Hasher};
209-
use std::convert::TryInto;
210209

211210
use pyo3::exceptions::{PyValueError, PyZeroDivisionError};
212211
use pyo3::prelude::*;

guide/src/class/object.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ impl Number {
217217

218218
# fn main() -> PyResult<()> {
219219
# Python::with_gil(|py| {
220-
# let x = PyCell::new(py, Number(4))?;
221-
# let y = PyCell::new(py, Number(4))?;
220+
# let x = &Bound::new(py, Number(4))?.into_any();
221+
# let y = &Bound::new(py, Number(4))?.into_any();
222222
# assert!(x.eq(y)?);
223223
# assert!(!x.ne(y)?);
224224
# Ok(())

guide/src/class/protocols.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl Container {
207207

208208
# Python::with_gil(|py| {
209209
# let container = Container { iter: vec![1, 2, 3, 4] };
210-
# let inst = pyo3::PyCell::new(py, container).unwrap();
210+
# let inst = pyo3::Py::new(py, container).unwrap();
211211
# pyo3::py_run!(py, inst, "assert list(inst) == [1, 2, 3, 4]");
212212
# pyo3::py_run!(py, inst, "assert list(iter(iter(inst))) == [1, 2, 3, 4]");
213213
# });

guide/src/exception.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use pyo3::exceptions::PyException;
2424
create_exception!(mymodule, CustomError, PyException);
2525

2626
Python::with_gil(|py| {
27-
let ctx = [("CustomError", py.get_type::<CustomError>())].into_py_dict_bound(py);
27+
let ctx = [("CustomError", py.get_type_bound::<CustomError>())].into_py_dict_bound(py);
2828
pyo3::py_run!(
2929
py,
3030
*ctx,
@@ -46,7 +46,7 @@ pyo3::create_exception!(mymodule, CustomError, PyException);
4646
#[pymodule]
4747
fn mymodule(py: Python<'_>, m: &PyModule) -> PyResult<()> {
4848
// ... other elements added to module ...
49-
m.add("CustomError", py.get_type::<CustomError>())?;
49+
m.add("CustomError", py.get_type_bound::<CustomError>())?;
5050

5151
Ok(())
5252
}

guide/src/function.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
8383

8484
```rust
8585
use pyo3::prelude::*;
86+
use pyo3::types::PyString;
8687

8788
#[pyfunction]
8889
#[pyo3(pass_module)]
89-
fn pyfunction_with_module(module: &PyModule) -> PyResult<&str> {
90+
fn pyfunction_with_module<'py>(module: &Bound<'py, PyModule>) -> PyResult<Bound<'py, PyString>> {
9091
module.name()
9192
}
9293

guide/src/migration.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,28 @@ Because the new `Bound<T>` API brings ownership out of the PyO3 framework and in
217217
- `Bound<PyList>` and `Bound<PyTuple>` cannot support indexing with `list[0]`, you should use `list.get_item(0)` instead.
218218
- `Bound<PyTuple>::iter_borrowed` is slightly more efficient than `Bound<PyTuple>::iter`. The default iteration of `Bound<PyTuple>` cannot return borrowed references because Rust does not (yet) have "lending iterators". Similarly `Bound<PyTuple>::get_borrowed_item` is more efficient than `Bound<PyTuple>::get_item` for the same reason.
219219
- `&Bound<T>` does not implement `FromPyObject` (although it might be possible to do this in the future once the GIL Refs API is completely removed). Use `bound_any.downcast::<T>()` instead of `bound_any.extract::<&Bound<T>>()`.
220+
- To convert between `&PyAny` and `&Bound<PyAny>` you can use the `as_borrowed()` method:
221+
222+
```rust,ignore
223+
let gil_ref: &PyAny = ...;
224+
let bound: &Bound<PyAny> = &gil_ref.as_borrowed();
225+
```
226+
227+
> Because of the ownership changes, code which uses `.as_ptr()` to convert `&PyAny` and other GIL Refs to a `*mut pyo3_ffi::PyObject` should take care to avoid creating dangling pointers now that `Bound<PyAny>` carries ownership.
228+
>
229+
> For example, the following pattern with `Option<&PyAny>` can easily create a dangling pointer when migrating to the `Bound<PyAny>` smart pointer:
230+
>
231+
> ```rust,ignore
232+
> let opt: Option<&PyAny> = ...;
233+
> let p: *mut ffi::PyObject = opt.map_or(std::ptr::null_mut(), |any| any.as_ptr());
234+
> ```
235+
>
236+
> The correct way to migrate this code is to use `.as_ref()` to avoid dropping the `Bound<PyAny>` in the `map_or` closure:
237+
>
238+
> ```rust,ignore
239+
> let opt: Option<Bound<PyAny>> = ...;
240+
> let p: *mut ffi::PyObject = opt.as_ref().map_or(std::ptr::null_mut(), Bound::as_ptr);
241+
> ```
220242
221243
#### Migrating `FromPyObject` implementations
222244
@@ -1295,7 +1317,7 @@ impl Names {
12951317
}
12961318
}
12971319
# Python::with_gil(|py| {
1298-
# let names = PyCell::new(py, Names::new()).unwrap();
1320+
# let names = Py::new(py, Names::new()).unwrap();
12991321
# pyo3::py_run!(py, names, r"
13001322
# try:
13011323
# names.merge(names)
@@ -1330,7 +1352,7 @@ let obj_ref = PyRef::new(py, MyClass {}).unwrap();
13301352
```
13311353

13321354
After:
1333-
```rust
1355+
```rust,ignore
13341356
# use pyo3::prelude::*;
13351357
# #[pyclass]
13361358
# struct MyClass {}

guide/src/python_from_rust.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ quickly testing your Python extensions.
181181

182182
```rust
183183
use pyo3::prelude::*;
184-
use pyo3::{PyCell, py_run};
184+
use pyo3::py_run;
185185

186186
# fn main() {
187187
#[pyclass]
@@ -206,7 +206,7 @@ Python::with_gil(|py| {
206206
id: 34,
207207
name: "Yu".to_string(),
208208
};
209-
let userdata = PyCell::new(py, userdata).unwrap();
209+
let userdata = Py::new(py, userdata).unwrap();
210210
let userdata_as_tuple = (34, "Yu");
211211
py_run!(py, userdata userdata_as_tuple, r#"
212212
assert repr(userdata) == "User Yu(id: 34)"
@@ -228,7 +228,7 @@ to this function!
228228
```rust
229229
use pyo3::{
230230
prelude::*,
231-
types::{IntoPyDict, PyModule},
231+
types::IntoPyDict,
232232
};
233233

234234
# fn main() -> PyResult<()> {
@@ -414,7 +414,7 @@ fn main() -> PyResult<()> {
414414
let path = Path::new("/usr/share/python_app");
415415
let py_app = fs::read_to_string(path.join("app.py"))?;
416416
let from_python = Python::with_gil(|py| -> PyResult<Py<PyAny>> {
417-
let syspath: &PyList = py.import("sys")?.getattr("path")?.downcast()?;
417+
let syspath = py.import_bound("sys")?.getattr("path")?.downcast_into::<PyList>()?;
418418
syspath.insert(0, &path)?;
419419
let app: Py<PyAny> = PyModule::from_code(py, &py_app, "", "")?
420420
.getattr("run")?
@@ -437,7 +437,6 @@ Use context managers by directly invoking `__enter__` and `__exit__`.
437437

438438
```rust
439439
use pyo3::prelude::*;
440-
use pyo3::types::PyModule;
441440

442441
fn main() {
443442
Python::with_gil(|py| {
@@ -479,7 +478,7 @@ class House(object):
479478
}
480479
Err(e) => {
481480
house
482-
.call_method1("__exit__", (e.get_type_bound(py), e.value(py), e.traceback_bound(py)))
481+
.call_method1("__exit__", (e.get_type_bound(py), e.value_bound(py), e.traceback_bound(py)))
483482
.unwrap();
484483
}
485484
}
@@ -498,7 +497,7 @@ use pyo3::prelude::*;
498497

499498
# fn main() -> PyResult<()> {
500499
Python::with_gil(|py| -> PyResult<()> {
501-
let signal = py.import("signal")?;
500+
let signal = py.import_bound("signal")?;
502501
// Set SIGINT to have the default action
503502
signal
504503
.getattr("signal")?

0 commit comments

Comments
 (0)