Skip to content

Commit bb81384

Browse files
patrick91codeflash-ai[bot]pre-commit-ci[bot]
authored
Add support for @defer and @stream (#3819)
* Add imports for experimental execution and some initial types * Progress with fake data * Fix directive * Experimental execution disabled by default * Revert flag * Rename field so we don't get "did you mean" errors * Update schema * Mostly working already * Initial code for e2e tests * Structure * WIP * Allow failed * Push missing stuff * Update graphiql * It works if you return a blog post :D We need to un-hardcode the label * Workflow * Remove unused files * Non hard-coded labels * Add release notes * Fix type * Fix type * Implement Streamable * Fix annotation * ⚡️ Speed up function `process_result` by 100% in PR #3819 (`feature/defer`) (#3827) Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com> * Raise if not on graphql core 3.3.0 * Fix tests * Skip defer test * Manually print defer and stream directives * Support for GraphQL-core 3.3.0a9 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Type ignores * Fix test * Fix? * Docs * Fix? * Test * Final fix * Add tweet * Fix typo * Split tests * Refactor tests * Add failing test * WIP mask * Add test * Fix lint * "Fix" tests * Remove unused import * Note in the docs --------- Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 6f5c600 commit bb81384

File tree

84 files changed

+3595
-110
lines changed

Some content is hidden

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

84 files changed

+3595
-110
lines changed

.alexrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"special",
1414
"primitive",
1515
"invalid",
16-
"failed"
16+
"failed",
17+
"crash"
1718
]
1819
}

.github/workflows/e2e-tests.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: 🎭 E2E Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
paths:
9+
- "e2e/**"
10+
- ".github/workflows/e2e-tests.yml"
11+
12+
jobs:
13+
e2e-tests:
14+
name: 🎭 Run E2E Tests
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Setup Bun
21+
uses: oven-sh/setup-bun@v2
22+
23+
- name: Install dependencies
24+
run: |
25+
cd e2e
26+
bun install
27+
28+
- name: Install Playwright browsers
29+
run: |
30+
cd e2e
31+
bunx playwright install --with-deps
32+
33+
- name: Setup Python
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: "3.12"
37+
38+
- name: Install Poetry
39+
uses: snok/install-poetry@v1
40+
with:
41+
version: latest
42+
virtualenvs-create: true
43+
virtualenvs-in-project: true
44+
45+
- name: Install Python dependencies
46+
run: |
47+
poetry install --extras debug-server
48+
poetry run pip install graphql-core==3.3.0a9
49+
50+
- name: Start Strawberry server
51+
run: |
52+
cd e2e
53+
poetry run strawberry server app:schema --port 8000 &
54+
echo $! > server.pid
55+
sleep 5 # Wait for server to start
56+
57+
- name: Check if server is running
58+
run: |
59+
curl -f http://localhost:8000/graphql || (echo "Server is not running" && exit 1)
60+
echo "GraphQL server is running successfully"
61+
62+
- name: Run Playwright tests
63+
run: |
64+
cd e2e
65+
bunx playwright test
66+
67+
- uses: actions/upload-artifact@v4
68+
if: always()
69+
with:
70+
name: playwright-report
71+
path: e2e/playwright-report/
72+
retention-days: 30

RELEASE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Release type: minor
2+
3+
This release adds experimental support for GraphQL's `@defer` and `@stream` directives, enabling incremental delivery of response data.
4+
5+
Note: this only works when using Strawberry with `graphql-core>=3.3.0a9`.
6+
7+
## Features
8+
9+
- **`@defer` directive**: Allows fields to be resolved asynchronously and delivered incrementally
10+
- **`@stream` directive**: Enables streaming of list fields using the new `strawberry.Streamable` type
11+
- **`strawberry.Streamable[T]`**: A new generic type for defining streamable fields that work with `@stream`
12+
13+
## Configuration
14+
15+
To enable these experimental features, configure your schema with:
16+
17+
```python
18+
from strawberry.schema.config import StrawberryConfig
19+
20+
schema = strawberry.Schema(
21+
query=Query, config=StrawberryConfig(enable_experimental_incremental_execution=True)
22+
)
23+
```

TWEET.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
🆕 Release $version is out! Thanks to $contributor for the PR 👏
2+
3+
This release adds experimental support for GraphQL's @defer and @stream directives, enabling incremental delivery of response data! 🚀
4+
5+
Get it here 👉 $release_url

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ title: Strawberry docs
3535
- [Lazy types](./types/lazy.md)
3636
- [Exceptions](./types/exceptions.md)
3737
- [Private/External Fields](./types/private.md)
38+
- [Defer and Stream](./types/defer-and-stream.md)
3839

3940
## Codegen
4041

docs/types/defer-and-stream.md

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
---
2+
title: Defer and Stream
3+
---
4+
5+
# Defer and Stream
6+
7+
Strawberry provides experimental support for GraphQL's `@defer` and `@stream`
8+
directives, which enable incremental delivery of response data. These directives
9+
allow parts of a GraphQL response to be delivered as they become available,
10+
rather than waiting for the entire response to be ready.
11+
12+
<Note>
13+
14+
This feature requires `graphql-core>=3.3.0a9` and is currently experimental. The
15+
API and behavior may change in future releases.
16+
17+
**Important limitations:**
18+
19+
- Extensions (most importantly `MaskErrors`) are not fully supported yet.
20+
Extensions currently only process the initial result and do not handle
21+
incremental payloads delivered by `@defer` and `@stream`.
22+
- This means error masking and other extension functionality will only apply to
23+
the initial response, not to deferred or streamed data.
24+
25+
</Note>
26+
27+
## Enabling Defer and Stream
28+
29+
To use `@defer` and `@stream` directives, you need to enable experimental
30+
incremental execution in your schema configuration:
31+
32+
```python
33+
import strawberry
34+
from strawberry.schema.config import StrawberryConfig
35+
36+
37+
@strawberry.type
38+
class Query:
39+
# Your query fields here
40+
pass
41+
42+
43+
schema = strawberry.Schema(
44+
query=Query, config=StrawberryConfig(enable_experimental_incremental_execution=True)
45+
)
46+
```
47+
48+
## Using @defer
49+
50+
The `@defer` directive allows you to mark parts of a query to be resolved
51+
asynchronously. The initial response will include all non-deferred fields,
52+
followed by incremental payloads containing the deferred data.
53+
54+
### Example
55+
56+
```python
57+
import asyncio
58+
import strawberry
59+
60+
61+
@strawberry.type
62+
class Author:
63+
id: strawberry.ID
64+
name: str
65+
bio: str
66+
67+
68+
@strawberry.type
69+
class Article:
70+
id: strawberry.ID
71+
title: str
72+
content: str
73+
74+
@strawberry.field
75+
async def author(self) -> Author:
76+
# Simulate an expensive operation
77+
await asyncio.sleep(2)
78+
return Author(
79+
id=strawberry.ID("1"),
80+
name="Jane Doe",
81+
bio="A passionate writer and developer.",
82+
)
83+
84+
85+
@strawberry.type
86+
class Query:
87+
@strawberry.field
88+
async def article(self, id: strawberry.ID) -> Article:
89+
return Article(
90+
id=id,
91+
title="Introduction to GraphQL Defer",
92+
content="Learn how to use the @defer directive...",
93+
)
94+
```
95+
96+
With this schema, you can query with `@defer`:
97+
98+
```graphql
99+
query GetArticle {
100+
article(id: "123") {
101+
id
102+
title
103+
content
104+
... on Article @defer {
105+
author {
106+
id
107+
name
108+
bio
109+
}
110+
}
111+
}
112+
}
113+
```
114+
115+
The response will be delivered incrementally:
116+
117+
```json
118+
# Initial payload
119+
{
120+
"data": {
121+
"article": {
122+
"id": "123",
123+
"title": "Introduction to GraphQL Defer",
124+
"content": "Learn how to use the @defer directive..."
125+
}
126+
},
127+
"hasNext": true
128+
}
129+
130+
# Subsequent payload
131+
{
132+
"incremental": [{
133+
"data": {
134+
"author": {
135+
"id": "1",
136+
"name": "Jane Doe",
137+
"bio": "A passionate writer and developer."
138+
}
139+
},
140+
"path": ["article"]
141+
}],
142+
"hasNext": false
143+
}
144+
```
145+
146+
## Using @stream with strawberry.Streamable
147+
148+
The `@stream` directive works with list fields and allows items to be delivered
149+
as they become available. Strawberry provides a special `Streamable` type
150+
annotation for fields that can be streamed.
151+
152+
### Example
153+
154+
```python
155+
import asyncio
156+
import strawberry
157+
from typing import AsyncGenerator
158+
159+
160+
@strawberry.type
161+
class Comment:
162+
id: strawberry.ID
163+
content: str
164+
author_name: str
165+
166+
167+
@strawberry.type
168+
class BlogPost:
169+
id: strawberry.ID
170+
title: str
171+
172+
@strawberry.field
173+
async def comments(self) -> strawberry.Streamable[Comment]:
174+
"""Stream comments as they are fetched from the database."""
175+
for i in range(5):
176+
# Simulate fetching comments from a database
177+
await asyncio.sleep(0.5)
178+
yield Comment(
179+
id=strawberry.ID(f"comment-{i}"),
180+
content=f"This is comment number {i}",
181+
author_name=f"User {i}",
182+
)
183+
```
184+
185+
Query with `@stream`:
186+
187+
```graphql
188+
query GetBlogPost {
189+
blogPost(id: "456") {
190+
id
191+
title
192+
comments @stream(initialCount: 2) {
193+
id
194+
content
195+
authorName
196+
}
197+
}
198+
}
199+
```
200+
201+
The response will stream the comments:
202+
203+
```json
204+
# Initial payload with first 2 comments
205+
{
206+
"data": {
207+
"blogPost": {
208+
"id": "456",
209+
"title": "My Blog Post",
210+
"comments": [
211+
{
212+
"id": "comment-0",
213+
"content": "This is comment number 0",
214+
"authorName": "User 0"
215+
},
216+
{
217+
"id": "comment-1",
218+
"content": "This is comment number 1",
219+
"authorName": "User 1"
220+
}
221+
]
222+
}
223+
},
224+
"hasNext": true
225+
}
226+
227+
# Subsequent payloads for remaining comments
228+
{
229+
"incremental": [{
230+
"items": [{
231+
"id": "comment-2",
232+
"content": "This is comment number 2",
233+
"authorName": "User 2"
234+
}],
235+
"path": ["blogPost", "comments", 2]
236+
}],
237+
"hasNext": true
238+
}
239+
# ... more incremental payloads
240+
```

docs/types/operation-directives.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,36 @@ All Directives are proceeded by `@` symbol
1616

1717
# Default Operation directives
1818

19-
Strawberry provides two default operation directives:
19+
Strawberry provides the following default operation directives:
2020

2121
- `@skip(if: Boolean!)` - if Boolean is true, the given item is NOT resolved by
2222
the GraphQL Server
2323

2424
- `@include(if: Boolean!)` - if Boolean is false, the given item is NOT resolved
2525
by the GraphQL Server
2626

27+
## Experimental Directives
28+
29+
When
30+
[experimental incremental execution](./schema-configurations#enable_experimental_incremental_execution)
31+
is enabled, these additional directives become available:
32+
33+
- `@defer(if: Boolean, label: String)` - Allows fields to be resolved
34+
asynchronously and delivered incrementally. The field will be omitted from the
35+
initial response and sent in a subsequent payload.
36+
37+
- `@stream(if: Boolean, label: String, initialCount: Int)` - Enables streaming
38+
of list fields. The list will be delivered incrementally, with `initialCount`
39+
items in the initial response and remaining items in subsequent payloads.
40+
41+
<Note>
42+
43+
These experimental directives require `graphql-core>=3.3.0a9` and must be
44+
enabled via schema configuration. See [Defer and Stream](./defer-and-stream) for
45+
detailed usage information.
46+
47+
</Note>
48+
2749
<Note>
2850

2951
`@deprecated(reason: String)` IS NOT compatible with Operation directives.

0 commit comments

Comments
 (0)