Implement minimal builtins.anext() by YashSuthar983 · Pull Request #6226 · RustPython/RustPython
issue : #3609
Currently passes
import builtins import asyncio async def agen(): for i in range(3): yield i async def main(): aiter = agen() val1 = await builtins.anext(aiter) print("val1:", val1) val2 = await builtins.anext(aiter) print("val2:", val2) val3 = await builtins.anext(aiter, "done") print("val3:", val3) asyncio.run(main())
Failed case ,need PyAnextAwaitable
import asyncio async def simple_test(): async def agen(): yield 1 aiter = agen() print(f"First value: {await anext(aiter)}") # EXPECTED: Should return "no more values" result = await anext(aiter, "no more values") print(f"Second value: {result}") if __name__ == "__main__": asyncio.run(simple_test())
#Cpython First value: 1 Second value: no more values #RustPython Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s Running `target/debug/rustpython /home/yash/test_anext_simple.py` First value: 1 Traceback (most recent call last): File "/home/yash/test_anext_simple.py", line 22, in <module> asyncio.run(simple_test()) File "/home/yash/contri/RustPython/pylib/Lib/asyncio/runners.py", line 193, in run with Runner(debug=debug, loop_factory=loop_factory) as runner: File "/home/yash/contri/RustPython/pylib/Lib/asyncio/runners.py", line 194, in run return runner.run(main) File "/home/yash/contri/RustPython/pylib/Lib/asyncio/runners.py", line 129, in run signal.signal(signal.SIGINT, signal.default_int_handler) File "/home/yash/contri/RustPython/pylib/Lib/asyncio/runners.py", line 124, in run raise # CancelledError File "/home/yash/contri/RustPython/pylib/Lib/asyncio/runners.py", line 118, in run return self._loop.run_until_complete(task) File "/home/yash/contri/RustPython/pylib/Lib/asyncio/base_events.py", line 687, in run_until_complete return future.result() File "/home/yash/contri/RustPython/pylib/Lib/asyncio/futures.py", line 203, in result raise self._exception.with_traceback(self._exception_tb) File "/home/yash/contri/RustPython/pylib/Lib/asyncio/tasks.py", line 314, in __step_run_and_handle_result result = coro.send(None) File "/home/yash/test_anext_simple.py", line 18, in simple_test result = await anext(aiter, "no more values") StopAsyncIteration
Summary by CodeRabbit
- New Features
- Added
anextbuiltin function to retrieve the next item from asynchronous iterators with optional default value support.
- Added
issue : RustPython#3609 Signed-off-by: Yash Suthar <yashsuthar983@gmail.com>
Important
Review skipped
Review was skipped due to path filters
⛔ Files ignored due to path filters (2)
Lib/test/test_asyncgen.pyis excluded by!Lib/**Lib/test/test_contextlib_async.pyis excluded by!Lib/**
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Walkthrough
A new anext builtin function is added to the RustPython virtual machine's builtins module. The function accepts an asynchronous iterator and an optional default value, calling the iterator's __anext__ method to retrieve an awaitable. Default value handling is incomplete and marked with a TODO comment.
Changes
| Cohort / File(s) | Summary |
|---|---|
Async Iterator Advancement vm/src/stdlib/builtins.rs |
Added anext() builtin function that invokes __anext__ on an async iterator to obtain an awaitable; default value parameter is accepted but not yet fully implemented |
Sequence Diagram
sequenceDiagram
participant User as User Code
participant Builtin as anext() Builtin
participant Iterator as Async Iterator
participant Awaitable as __anext__ Awaitable
User->>Builtin: anext(aiter, [default_value])
Builtin->>Iterator: Access __anext__ method
Iterator->>Awaitable: Return awaitable
Awaitable-->>Builtin: Awaitable object
alt default_value provided
Note over Builtin: TODO: Handle default value<br/>(incomplete)
end
Builtin-->>User: Return awaitable
Estimated code review effort
🎯 2 (Simple) | ⏱️ ~10 minutes
- Single new function with straightforward logic flow
- Incomplete default value handling marked as TODO—clarify intended behavior with PyAnextAwaitable wrapper
- Verify proper integration with existing
__anext__protocol and async iterator infrastructure
Poem
🐰 A rabbit hops through async streams so fine,
Withanext()now in place—the functions align!
Though defaults await their complete design,
Await-ables flow where iterators shine. ✨
Pre-merge checks and finishing touches
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title Check | ✅ Passed | The title "Implement minimal builtins.anext()" directly and accurately describes the main change in the pull request, which is the addition of a new anext builtin function to the builtins module. The title is concise, clear, and uses the word "minimal" to appropriately reflect that the implementation is incomplete (specifically lacking full default_value handling as noted in the PR objectives). The title is specific enough that a teammate reviewing the git history would immediately understand that a new builtin function was added, rather than other types of changes like bug fixes or refactorings. |
| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. |
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
@youknowone can you provide some direction to implement PyAnextAwaitable.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
vm/src/stdlib/builtins.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Format Rust code with the default rustfmt style (runcargo fmt)
Run clippy and fix any warnings or lints introduced by your changes
Follow Rust best practices for error handling and memory management
Files:
vm/src/stdlib/builtins.rs
{vm,stdlib}/**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use RustPython macros (
pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
vm/src/stdlib/builtins.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Check the WASM package and demo
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run tests under miri
- GitHub Check: Check Rust code with rustfmt and clippy
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Ensure compilation on various targets
Comment on lines +537 to +551
| #[pyfunction] | ||
| fn anext( | ||
| aiter: PyObjectRef, | ||
| default_value: OptionalArg<PyObjectRef>, | ||
| vm: &VirtualMachine, | ||
| ) -> PyResult { | ||
| let awaitable = vm.call_method(&aiter, "__anext__", ())?; | ||
|
|
||
| if default_value.is_missing() { | ||
| Ok(awaitable) | ||
| } else { | ||
| // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value. | ||
| Ok(awaitable) | ||
| } | ||
| } |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete default value handling makes the function only partially functional.
The default_value parameter is accepted but completely ignored—both branches return the same awaitable regardless of whether a default is provided. This causes the function to fail the second use case described in the PR objectives: when the async iterator is exhausted and a default value is provided, RustPython raises StopAsyncIteration instead of returning the default like CPython does.
While the TODO comment acknowledges this limitation, having a function signature that accepts a parameter it cannot honor is problematic because:
- Users expect CPython-compatible behavior
- The silent acceptance of
default_valuecreates a false sense of functionality - It's better to fail fast than fail silently
Consider one of these approaches before merging:
Option 1 (preferred if feasible): Complete the implementation by adding PyAnextAwaitable to wrap the awaitable and handle the default value case.
Option 2: Reject the default parameter explicitly until support is implemented:
#[pyfunction]
fn anext(
aiter: PyObjectRef,
default_value: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult {
let awaitable = vm.call_method(&aiter, "__anext__", ())?;
- if default_value.is_missing() {
- Ok(awaitable)
- } else {
- // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value.
- Ok(awaitable)
+ if let OptionalArg::Present(_) = default_value {
+ Err(vm.new_not_implemented_error(
+ "anext() with default value is not yet implemented".to_owned()
+ ))
+ } else {
+ Ok(awaitable)
}
}Option 3: Defer merging this PR until the default value handling is fully implemented.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #[pyfunction] | |
| fn anext( | |
| aiter: PyObjectRef, | |
| default_value: OptionalArg<PyObjectRef>, | |
| vm: &VirtualMachine, | |
| ) -> PyResult { | |
| let awaitable = vm.call_method(&aiter, "__anext__", ())?; | |
| if default_value.is_missing() { | |
| Ok(awaitable) | |
| } else { | |
| // TODO: Implement CPython like PyAnextAwaitable to properly handle the default value. | |
| Ok(awaitable) | |
| } | |
| } | |
| #[pyfunction] | |
| fn anext( | |
| aiter: PyObjectRef, | |
| default_value: OptionalArg<PyObjectRef>, | |
| vm: &VirtualMachine, | |
| ) -> PyResult { | |
| let awaitable = vm.call_method(&aiter, "__anext__", ())?; | |
| if let OptionalArg::Present(_) = default_value { | |
| Err(vm.new_not_implemented_error( | |
| "anext() with default value is not yet implemented".to_owned() | |
| )) | |
| } else { | |
| Ok(awaitable) | |
| } | |
| } |
Thanks! By looking the CI result, the test is failing because this patch fixed a few tests:
======================================================================
UNEXPECTED SUCCESS: test_anext_await_raises (test.test_asyncgen.AsyncGenAsyncioTest.test_anext_await_raises)
UNEXPECTED SUCCESS: test_anext_return_generator (test.test_asyncgen.AsyncGenAsyncioTest.test_anext_return_generator)
UNEXPECTED SUCCESS: test_anext_return_iterator (test.test_asyncgen.AsyncGenAsyncioTest.test_anext_return_iterator)
----------------------------------------------------------------------
Remove @expectedFailure from those tests will fix the CI.
For PyAnextAwaitable, I'd start to defining the type on vm/src/builtins/iter.rs using #[pyclass]. Searching #[pyclass in source code will give some idea about it. Please poke me when you are blocked by something.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍