Skip to content

Commit

Permalink
Add ability to return from __next__ / __anext__
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Jun 23, 2020
1 parent 0c59b05 commit e90a270
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add `PyByteArray::data`, `PyByteArray::as_bytes`, and `PyByteArray::as_bytes_mut`. [#967](https://github.com/PyO3/pyo3/pull/967)
- Add `GILOnceCell` to use in situations where `lazy_static` or `once_cell` can deadlock. [#975](https://github.com/PyO3/pyo3/pull/975)
- Add `Py::borrow`, `Py::borrow_mut`, `Py::try_borrow`, and `Py::try_borrow_mut` for accessing `#[pyclass]` values. [#976](https://github.com/PyO3/pyo3/pull/976)
- Add `IterNextOutput` and `IterANextOutput` for returning from `__next__` / `__anext__`. [#997](https://github.com/PyO3/pyo3/pull/997)

### Changed
- Simplify internals of `#[pyo3(get)]` attribute. (Remove the hidden API `GetPropertyValue`.) [#934](https://github.com/PyO3/pyo3/pull/934)
Expand Down
1 change: 1 addition & 0 deletions examples/rustapi_module/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def make_rust_extension(module_name):
make_rust_extension("rustapi_module.othermod"),
make_rust_extension("rustapi_module.subclassing"),
make_rust_extension("rustapi_module.test_dict"),
make_rust_extension("rustapi_module.pyclass_iter"),
],
install_requires=install_requires,
tests_require=tests_require,
Expand Down
1 change: 1 addition & 0 deletions examples/rustapi_module/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod datetime;
pub mod dict_iter;
pub mod objstore;
pub mod othermod;
pub mod pyclass_iter;
pub mod subclassing;
34 changes: 34 additions & 0 deletions examples/rustapi_module/src/pyclass_iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use pyo3::class::iter::{IterNextOutput, PyIterProtocol};
use pyo3::prelude::*;

/// This is for demonstrating how to return a value from __next__
#[pyclass]
struct PyClassIter {
count: usize,
}

#[pymethods]
impl PyClassIter {
#[new]
pub fn new() -> Self {
PyClassIter { count: 0 }
}
}

#[pyproto]
impl PyIterProtocol for PyClassIter {
fn __next__(mut slf: PyRefMut<Self>) -> IterNextOutput {
if slf.count < 5 {
slf.count += 1;
IterNextOutput::Yield(slf.count.into_py(slf.py()))
} else {
IterNextOutput::Return(Some("Ended".into_py(slf.py())))
}
}
}

#[pymodule]
pub fn pyclass_iter(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PyClassIter>()?;
Ok(())
}
14 changes: 14 additions & 0 deletions examples/rustapi_module/tests/test_pyclass_iter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest
from rustapi_module import pyclass_iter

def test_iter():
i = pyclass_iter.PyClassIter()
assert next(i) == 1
assert next(i) == 2
assert next(i) == 3
assert next(i) == 4
assert next(i) == 5

with pytest.raises(StopIteration) as excinfo:
next(i)
assert excinfo.value.value == "Ended"
25 changes: 20 additions & 5 deletions src/class/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,37 @@ impl PyIterMethods {
}
}

pub struct IterNextOutput(Option<PyObject>);
pub enum IterNextOutput {
Yield(PyObject),
Return(Option<PyObject>),
}

impl IntoPyCallbackOutput<*mut ffi::PyObject> for IterNextOutput {
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
match self.0 {
Some(o) => Ok(o.into_ptr()),
None => Err(crate::exceptions::StopIteration::py_err(())),
match self {
IterNextOutput::Yield(o) => Ok(o.into_ptr()),
IterNextOutput::Return(opt) => match opt {
Some(o) => Err(crate::exceptions::StopIteration::py_err((o,))),
None => Err(crate::exceptions::StopIteration::py_err(())),
},
}
}
}

impl IntoPyCallbackOutput<IterNextOutput> for IterNextOutput {
fn convert(self, _py: Python) -> PyResult<IterNextOutput> {
Ok(self)
}
}

impl<T> IntoPyCallbackOutput<IterNextOutput> for Option<T>
where
T: IntoPy<PyObject>,
{
fn convert(self, py: Python) -> PyResult<IterNextOutput> {
Ok(IterNextOutput(self.map(|o| o.into_py(py))))
match self {
Some(o) => Ok(IterNextOutput::Yield(o.into_py(py))),
None => Ok(IterNextOutput::Return(None)),
}
}
}
25 changes: 20 additions & 5 deletions src/class/pyasync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,23 +107,38 @@ impl ffi::PyAsyncMethods {
}
}

pub struct IterANextOutput(Option<PyObject>);
pub enum IterANextOutput {
Yield(PyObject),
Return(Option<PyObject>),
}

impl IntoPyCallbackOutput<*mut ffi::PyObject> for IterANextOutput {
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
match self.0 {
Some(o) => Ok(o.into_ptr()),
None => Err(crate::exceptions::StopAsyncIteration::py_err(())),
match self {
IterANextOutput::Yield(o) => Ok(o.into_ptr()),
IterANextOutput::Return(opt) => match opt {
Some(o) => Err(crate::exceptions::StopAsyncIteration::py_err((o,))),
None => Err(crate::exceptions::StopAsyncIteration::py_err(())),
},
}
}
}

impl IntoPyCallbackOutput<IterANextOutput> for IterANextOutput {
fn convert(self, _py: Python) -> PyResult<IterANextOutput> {
Ok(self)
}
}

impl<T> IntoPyCallbackOutput<IterANextOutput> for Option<T>
where
T: IntoPy<PyObject>,
{
fn convert(self, py: Python) -> PyResult<IterANextOutput> {
Ok(IterANextOutput(self.map(|o| o.into_py(py))))
match self {
Some(o) => Ok(IterANextOutput::Yield(o.into_py(py))),
None => Ok(IterANextOutput::Return(None)),
}
}
}

Expand Down

0 comments on commit e90a270

Please sign in to comment.