Skip to content

Commit 01a2c63

Browse files
committed
Implement Streamable
1 parent a64b402 commit 01a2c63

File tree

5 files changed

+67
-9
lines changed

5 files changed

+67
-9
lines changed

e2e/app.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import asyncio # noqa: INP001
2+
import random
23

34
import strawberry
45
from strawberry.schema.config import StrawberryConfig
56

67

8+
@strawberry.type
9+
class Author:
10+
id: strawberry.ID
11+
name: str
12+
13+
714
@strawberry.type
815
class Comment:
916
id: strawberry.ID
1017
content: str
1118

19+
@strawberry.field
20+
async def author(self) -> Author:
21+
await asyncio.sleep(random.randint(0, 2)) # noqa: S311
22+
return Author(id=strawberry.ID("Author:1"), name="John Doe")
23+
1224

1325
@strawberry.type
1426
class BlogPost:
@@ -17,12 +29,11 @@ class BlogPost:
1729
content: str
1830

1931
@strawberry.field
20-
async def comments(self) -> list[Comment]:
21-
await asyncio.sleep(2)
22-
return [
23-
Comment(id=strawberry.ID("Comment:1"), content="Great post!"),
24-
Comment(id=strawberry.ID("Comment:2"), content="Thanks for sharing!"),
25-
]
32+
async def comments(self) -> strawberry.Streamable[Comment]:
33+
for x in range(5):
34+
await asyncio.sleep(random.choice([0, 0.5, 1, 1.5, 2])) # noqa: S311
35+
36+
yield Comment(id=strawberry.ID(f"Comment:{x}"), content="Great post!")
2637

2738

2839
@strawberry.type

strawberry/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .scalars import ID
1212
from .schema import Schema
1313
from .schema_directive import schema_directive
14+
from .streamable import Streamable
1415
from .types.arguments import argument
1516
from .types.auto import auto
1617
from .types.cast import cast
@@ -34,6 +35,7 @@
3435
"Parent",
3536
"Private",
3637
"Schema",
38+
"Streamable",
3739
"argument",
3840
"asdict",
3941
"auto",

strawberry/annotation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from typing_extensions import Self, get_args, get_origin
1919

20+
from strawberry.streamable import StrawberryStreamable
2021
from strawberry.types.base import (
2122
StrawberryList,
2223
StrawberryObjectDefinition,
@@ -141,6 +142,8 @@ def _resolve(self) -> Union[StrawberryType, type]:
141142

142143
if self._is_lazy_type(evaled_type):
143144
return evaled_type
145+
if self._is_streamable(evaled_type, args):
146+
return self.create_list(list[evaled_type])
144147
if self._is_list(evaled_type):
145148
return self.create_list(evaled_type)
146149

@@ -310,6 +313,10 @@ def _is_list(cls, annotation: Any) -> bool:
310313
or is_list
311314
)
312315

316+
@classmethod
317+
def _is_streamable(cls, annotation: Any, args: list[Any]) -> bool:
318+
return any(isinstance(arg, StrawberryStreamable) for arg in args)
319+
313320
@classmethod
314321
def _is_strawberry_type(cls, evaled_type: Any) -> bool:
315322
# Prevent import cycles

strawberry/http/async_base_view.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ async def stream() -> AsyncGenerator[str, None]:
367367

368368
yield self.encode_multipart_data(response, "-")
369369

370+
all_pending = result.initial_result.pending
371+
370372
async for value in result.subsequent_results:
371373
response = {
372374
"hasNext": value.has_next,
@@ -382,11 +384,13 @@ async def stream() -> AsyncGenerator[str, None]:
382384
if value.incremental:
383385
incremental = []
384386

387+
all_pending.extend(value.pending)
388+
385389
for incremental_value in value.incremental:
386390
pending_value = next(
387391
(
388392
v
389-
for v in result.initial_result.pending
393+
for v in all_pending
390394
if v.id == incremental_value.id
391395
),
392396
None,
@@ -397,8 +401,6 @@ async def stream() -> AsyncGenerator[str, None]:
397401
incremental.append(
398402
{
399403
**incremental_value.formatted,
400-
# for Apollo
401-
# content type is `multipart/mixed;deferSpec=20220824,application/json`
402404
"path": pending_value.path,
403405
"label": pending_value.label,
404406
}

strawberry/streamable.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from collections.abc import AsyncGenerator
2+
from typing import Annotated, TypeVar
3+
4+
5+
class StrawberryStreamable: ...
6+
7+
8+
T = TypeVar("T")
9+
10+
Streamable = Annotated[AsyncGenerator[T, None], StrawberryStreamable()]
11+
"""Represents a list that can be streamed using @stream.
12+
13+
Example:
14+
15+
```python
16+
import strawberry
17+
from dataclasses import dataclass
18+
19+
20+
@strawberry.type
21+
class Comment:
22+
id: strawberry.ID
23+
content: str
24+
25+
26+
@strawberry.type
27+
class Article:
28+
@strawberry.field
29+
@staticmethod
30+
async def comments() -> strawberry.Streamable[Comment]:
31+
for comment in fetch_comments():
32+
yield comment
33+
```
34+
"""
35+
36+
__all__ = ["Streamable"]

0 commit comments

Comments
 (0)