Skip to content

fix: handle Ray Serve streaming response generator correctly (#190)#191

Merged
touale merged 1 commit into
masterfrom
fix/stream-response-generator
Jun 12, 2026
Merged

fix: handle Ray Serve streaming response generator correctly (#190)#191
touale merged 1 commit into
masterfrom
fix/stream-response-generator

Conversation

@touale

@touale touale commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes
    • Fixed streaming response handling to correctly process async iterables without unnecessary await operations.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR refines async streaming normalization across the adapter and driver layers: both now correctly distinguish async-iterable responses from awaitables, awaiting only non-iterable awaitables and iterating async iterables directly. Tests validate that async-iterable streams are not inadvertently awaited.

Changes

Async-iterable Streaming Normalization

Layer / File(s) Summary
Async-iterable detection in BaseAdapter
src/framex/adapter/base.py
AsyncIterable is added to imports. BaseAdapter.call_func now awaits streamed results only when awaitable and not already an AsyncIterable, distinguishing the two response types at the source.
Streaming normalization in APIIngress
src/framex/driver/ingress.py
The same conditional-await pattern is applied in APIIngress.register_route: results are awaited only if awaitable and not async-iterable before iterating chunks in the StreamingResponse.
Test validation
tests/adapter/test_local_adapter.py, tests/driver/test_ingress.py
Test helpers (AwaitableAsyncStream) explicitly fail if awaited, and new tests verify that both call_func and register_route correctly iterate async-iterable streams without awaiting them.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • touale/FrameX-kit#177: Both PRs adjust the streaming normalization in BaseAdapter.call_func (and its use in APIIngress.register_route) to conditionally await streaming results instead of blindly iterating them, so the main PR's async-iterable-vs-awaitable handling is directly related to #177's streaming awaitable handling.

Poem

🐰 Async streams hop forward swift and free,
Not all awaiting—some just iterate!
The adapter now knows: await or not be,
When async-iterables congregate. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main fix: correcting the handling of Ray Serve streaming response generators, which directly corresponds to the changes across base adapter, ingress driver, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/stream-response-generator

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/adapter/test_local_adapter.py (2)

268-278: ⚡ Quick win

Add return type hint to the test function.

Per coding guidelines, "Include type hints on functions and methods in Python code."

♻️ Proposed fix
-    async def test_call_func_stream_does_not_await_async_iterable_response(self):
+    async def test_call_func_stream_does_not_await_async_iterable_response(self) -> None:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/adapter/test_local_adapter.py` around lines 268 - 278, The test
function test_call_func_stream_does_not_await_async_iterable_response is missing
a return type hint; update its signature to include a return annotation (->
None) per the project's type-hinting guidelines so the function reads with an
explicit return type, keeping the rest of the body unchanged and ensuring any
imports or typing requirements are already satisfied.

Source: Coding guidelines


12-27: ⚡ Quick win

Add type hints to the test helper class.

Per coding guidelines, "Include type hints on functions and methods in Python code." The AwaitableAsyncStream helper methods should include type hints for parameters and return values.

♻️ Proposed fix to add type hints
+from typing import Iterator, NoReturn
+
 class AwaitableAsyncStream:
-    def __init__(self, chunks):
+    def __init__(self, chunks: list[str]) -> None:
         self._chunks = iter(chunks)
 
-    def __await__(self):
+    def __await__(self) -> NoReturn:
         raise RuntimeError("stream response should not be awaited")
         yield  # pragma: no cover
 
-    def __aiter__(self):
+    def __aiter__(self) -> "AwaitableAsyncStream":
         return self
 
-    async def __anext__(self):
+    async def __anext__(self) -> str:
         try:
             return next(self._chunks)
         except StopIteration as exc:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/adapter/test_local_adapter.py` around lines 12 - 27, The
AwaitableAsyncStream test helper lacks type hints; add annotations to the class
and its methods: annotate __init__(self, chunks: Iterable[Any]) -> None,
__await__(self) -> Generator[None, None, Any] (or -> Any with appropriate
Generator type), __aiter__(self) -> "AwaitableAsyncStream" (or ->
AsyncIterator[Any]), and async def __anext__(self) -> Any (or -> Any) and import
any needed typing names (Iterable, Any, AsyncIterator, Generator) to satisfy the
coding guidelines and type checkers while keeping behavior unchanged.

Source: Coding guidelines

tests/driver/test_ingress.py (2)

209-214: ⚡ Quick win

Add return type hint to the test function.

Per coding guidelines, "Include type hints on functions and methods in Python code."

♻️ Proposed fix
-async def test_register_route_stream_does_not_await_async_iterable_response(ingress, mock_app):
+async def test_register_route_stream_does_not_await_async_iterable_response(ingress, mock_app) -> None:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/driver/test_ingress.py` around lines 209 - 214, Add a return type hint
to the test function
test_register_route_stream_does_not_await_async_iterable_response by annotating
its signature with "-> None" so it complies with the project's typing
guidelines; update the def for
test_register_route_stream_does_not_await_async_iterable_response (the one that
constructs AwaitableAsyncStream, calls register_stream_endpoint and asserts
collect_stream_response) to include the return type hint.

Source: Coding guidelines


191-207: ⚡ Quick win

Add type hints and consider extracting to shared test utility.

Per coding guidelines, "Include type hints on functions and methods in Python code." The AwaitableAsyncStream helper should include type hints.

Additionally, this helper is duplicated from tests/adapter/test_local_adapter.py (lines 12-27). Consider extracting it to a shared test utility module to reduce duplication and improve maintainability.

♻️ Proposed fix for type hints
+from typing import Iterator, NoReturn
+
 class AwaitableAsyncStream:
-    def __init__(self, chunks):
+    def __init__(self, chunks: list[str]) -> None:
         self._chunks = iter(chunks)
 
-    def __await__(self):
+    def __await__(self) -> NoReturn:
         raise RuntimeError("stream response should not be awaited")
         yield  # pragma: no cover
 
-    def __aiter__(self):
+    def __aiter__(self) -> "AwaitableAsyncStream":
         return self
 
-    async def __anext__(self):
+    async def __anext__(self) -> str:
         try:
             return next(self._chunks)
         except StopIteration as exc:

For the duplication concern, consider creating tests/helpers.py or tests/conftest.py and moving the helper there.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/driver/test_ingress.py` around lines 191 - 207, Add type hints to the
AwaitableAsyncStream helper and move it to a shared test utility to avoid
duplication: update the class AwaitableAsyncStream and its methods (__init__,
__await__, __aiter__, __anext__) to include precise typing (e.g., accept
Iterable[bytes] or Iterable[str] for chunks, annotate return types
Optional[Iterator], AsyncIterator, etc.), and then extract the class into a
common test utility module and import it from both tests so the duplicate
definition in tests/driver/test_ingress.py and
tests/adapter/test_local_adapter.py is removed.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/adapter/test_local_adapter.py`:
- Around line 268-278: The test function
test_call_func_stream_does_not_await_async_iterable_response is missing a return
type hint; update its signature to include a return annotation (-> None) per the
project's type-hinting guidelines so the function reads with an explicit return
type, keeping the rest of the body unchanged and ensuring any imports or typing
requirements are already satisfied.
- Around line 12-27: The AwaitableAsyncStream test helper lacks type hints; add
annotations to the class and its methods: annotate __init__(self, chunks:
Iterable[Any]) -> None, __await__(self) -> Generator[None, None, Any] (or -> Any
with appropriate Generator type), __aiter__(self) -> "AwaitableAsyncStream" (or
-> AsyncIterator[Any]), and async def __anext__(self) -> Any (or -> Any) and
import any needed typing names (Iterable, Any, AsyncIterator, Generator) to
satisfy the coding guidelines and type checkers while keeping behavior
unchanged.

In `@tests/driver/test_ingress.py`:
- Around line 209-214: Add a return type hint to the test function
test_register_route_stream_does_not_await_async_iterable_response by annotating
its signature with "-> None" so it complies with the project's typing
guidelines; update the def for
test_register_route_stream_does_not_await_async_iterable_response (the one that
constructs AwaitableAsyncStream, calls register_stream_endpoint and asserts
collect_stream_response) to include the return type hint.
- Around line 191-207: Add type hints to the AwaitableAsyncStream helper and
move it to a shared test utility to avoid duplication: update the class
AwaitableAsyncStream and its methods (__init__, __await__, __aiter__, __anext__)
to include precise typing (e.g., accept Iterable[bytes] or Iterable[str] for
chunks, annotate return types Optional[Iterator], AsyncIterator, etc.), and then
extract the class into a common test utility module and import it from both
tests so the duplicate definition in tests/driver/test_ingress.py and
tests/adapter/test_local_adapter.py is removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a82584b-1285-4ebb-a0e6-a1f58e653113

📥 Commits

Reviewing files that changed from the base of the PR and between 91cd587 and 0b280ae.

📒 Files selected for processing (4)
  • src/framex/adapter/base.py
  • src/framex/driver/ingress.py
  • tests/adapter/test_local_adapter.py
  • tests/driver/test_ingress.py

@touale touale merged commit 5ed2bbd into master Jun 12, 2026
8 checks passed
@touale touale deleted the fix/stream-response-generator branch June 12, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant