strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

Defer

Open patrick91 opened this issue 8 months ago β€’ 23 comments

Another attempt :D

Summary by Sourcery

Implement experimental incremental execution in the GraphQL schema to support deferred and streamed responses. Refactor the async run method to handle incremental execution results. Add end-to-end tests for Apollo and Relay clients, and introduce a new CI workflow for running these tests.

New Features:

  • Introduce experimental incremental execution support in the GraphQL schema, allowing for deferred and streamed responses.

Enhancements:

  • Refactor the async run method to handle GraphQLIncrementalExecutionResults, enabling streaming responses for incremental execution.

CI:

  • Add a new GitHub Actions workflow for running end-to-end tests using Playwright.

Documentation:

  • Add a README file for the e2e directory, providing setup and configuration instructions for React, TypeScript, and Vite.

Tests:

  • Add end-to-end tests for GraphQL queries using Apollo and Relay clients, including tests for deferred and streamed responses.

patrick91 avatar Mar 25 '25 23:03 patrick91

Reviewer's Guide

Integrates experimental incremental GraphQL execution by switching to graphql-core’s experimental_execute_incrementally when enabled; adds multipart streaming support in the async HTTP view; extends schema definitions and printer to include @defer and @stream directives; introduces Streamable annotations; and adds comprehensive incremental tests and E2E suites with a dedicated CI workflow.

Sequence diagram for incremental execution and streaming HTTP response

sequenceDiagram
    participant Client
    participant AsyncBaseView
    participant Schema
    participant experimental_execute_incrementally
    participant GraphQLIncrementalExecutionResults

    Client->>AsyncBaseView: HTTP GraphQL request
    AsyncBaseView->>Schema: execute(...)
    Schema->>experimental_execute_incrementally: execute if incremental enabled
    experimental_execute_incrementally-->>Schema: GraphQLIncrementalExecutionResults
    Schema-->>AsyncBaseView: GraphQLIncrementalExecutionResults
    AsyncBaseView->>AsyncBaseView: stream() (yields multipart chunks)
    AsyncBaseView-->>Client: multipart/mixed streaming response

ER diagram for BlogPost, Comment, and Author (E2E test schema)

erDiagram
    BLOGPOST ||--o{ COMMENT : has
    COMMENT }o--|| AUTHOR : written_by
    BLOGPOST {
        ID id
        STRING title
        STRING content
    }
    COMMENT {
        ID id
        STRING content
    }
    AUTHOR {
        ID id
        STRING name
    }

Class diagram for Streamable and related types

classDiagram
    class StrawberryStreamable
    class Streamable {
        <<Annotated[AsyncGenerator[T, None], StrawberryStreamable()]>>
    }
    class AsyncGenerator
    class T
    Streamable --|> AsyncGenerator : Annotated
    Streamable --* StrawberryStreamable : Annotated
    Streamable ..> T : generic

Class diagram for incremental execution integration in Schema

classDiagram
    class Schema {
        +execute(...)
        +execute_sync(...)
        +_handle_execution_result(...)
        +_parse_and_validate_async(...)
        -_schema
        -config
    }
    class StrawberryConfig {
        +enable_experimental_incremental_execution: bool
    }
    class GraphQLIncrementalExecutionResults
    class experimental_execute_incrementally
    Schema --> StrawberryConfig : config
    Schema ..> GraphQLIncrementalExecutionResults : uses
    Schema ..> experimental_execute_incrementally : uses

File-Level Changes

Change Details Files
Implement HTTP streaming of incremental GraphQL results
  • Detect GraphQLIncrementalExecutionResults in async_base_view.run and generate multipart/mixed streams
  • Refactor encode_multipart_data to pre-encode JSON, include Content-Length and charset headers
  • Update process_result in http/init.py to accept and early-return incremental results
strawberry/http/async_base_view.py
strawberry/http/__init__.py
Integrate experimental incremental execution into schema execution
  • Add enable_experimental_incremental_execution config flag and choose experimental_execute_incrementally when active
  • Handle GraphQLIncrementalExecutionResults early in _handle_execution_result
  • Update execute and execute_sync to switch execution function based on config
  • Adapt build_resolve_info signature for graphql-core 3.3 compatibility
strawberry/schema/schema.py
Introduce Streamable annotation support
  • Define StrawberryStreamable and Streamable alias in strawberry/streamable.py
  • Enhance annotation resolver to recognize Streamable types
  • Extend ResultType union to include GraphQLIncrementalExecutionResults
  • Expose GraphQLIncrementalExecutionResults and directives via _graphql_core
strawberry/streamable.py
strawberry/annotation.py
strawberry/schema/_graphql_core.py
Extend schema printer to include defer and stream directives
  • Guard access to strawberry-definition extension
  • Append @defer and @stream directive definitions when incremental execution is enabled
strawberry/printer/printer.py
Add end-to-end and incremental test suites with CI workflow
  • Create Playwright-based E2E tests for Apollo and Relay clients in e2e directory
  • Add incremental defer and stream HTTP tests under tests/http/incremental
  • Introduce GitHub Actions workflow 🎭 E2E Tests for running Playwright suite on PRs
  • Include setup docs and configs (README, tsconfigs, CI, noxfile bump)
.github/workflows/e2e-tests.yml
e2e/
tests/http/incremental/

Possibly linked issues

  • #2567: The PR implements first-class support for @stream/@defer, matching the issue's main requirements and initial steps.
  • #3082: The PR implements support for @stream and @defer, directly addressing the issue's request for these features.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an issue from a review comment by replying to it. You can also reply to a review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in the pull request body to generate a PR summary at any time exactly where you want it. You can also comment @sourcery-ai summary on the pull request to (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the pull request to resolve all Sourcery comments. Useful if you've already addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull request to dismiss all existing Sourcery reviews. Especially useful if you want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

  • Contact our support team for questions or feedback.
  • Visit our documentation for detailed guides and information.
  • Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.

sourcery-ai[bot] avatar Mar 25 '25 23:03 sourcery-ai[bot]

Apollo Federation Subgraph Compatibility Results

Federation 1 Support Federation 2 Support
_service🟒
@key (single)🟒
@key (multi)🟒
@key (composite)🟒
repeatable @key🟒
@requires🟒
@provides🟒
federated tracingπŸ”²
@link🟒
@shareable🟒
@tag🟒
@override🟒
@inaccessible🟒
@composeDirective🟒
@interfaceObject🟒

Learn more:

botberry avatar Mar 25 '25 23:03 botberry

Codecov Report

Attention: Patch coverage is 57.49235% with 139 lines in your changes missing coverage. Please review.

Project coverage is 94.40%. Comparing base (6f5c600) to head (bf42f98).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3819      +/-   ##
==========================================
- Coverage   94.76%   94.40%   -0.36%     
==========================================
  Files         520      527       +7     
  Lines       33971    34230     +259     
  Branches     1760     1785      +25     
==========================================
+ Hits        32193    32316     +123     
- Misses       1497     1625     +128     
- Partials      281      289       +8     
:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov[bot] avatar Mar 25 '25 23:03 codecov[bot]

CodSpeed Performance Report

Merging #3819 will not alter performance

Comparing feature/defer (bf42f98) with main (6f5c600)

Summary

βœ… 26 untouched benchmarks

codspeed-hq[bot] avatar Mar 25 '25 23:03 codspeed-hq[bot]

Pre-release

:wave:

Pre-release 0.276.0.dev.1752831589 [830ca949678760ed2e883a7768bc7fa97351cee9] has been released on PyPi! :rocket: You can try it by doing:

poetry add strawberry-graphql==0.276.0.dev.1752831589

botberry avatar Mar 31 '25 19:03 botberry

/pre-release

patrick91 avatar Mar 31 '25 19:03 patrick91

⚑️ Codeflash found optimizations for this PR

πŸ“„ 100% (1.00x) speedup for process_result in strawberry/http/__init__.py

⏱️ Runtime : 1.78 millisecond β†’ 892 microseconds (best of 28 runs)

I created a new dependent PR with the suggested changes. Please review:

  • #3827

If you approve, it will be merged into this PR (branch feature/defer).

codeflash-ai[bot] avatar Mar 31 '25 19:03 codeflash-ai[bot]

/pre-release

patrick91 avatar Apr 02 '25 08:04 patrick91

Screenshot 2025-04-02 at 17 30 34

Did a quick test on this and fairly sure I've got the following crash. Its possible I've misconfigured something, but this is best understanding on how to enable it

matclayton avatar Apr 02 '25 16:04 matclayton

This PR is now faster! πŸš€ @patrick91 accepted my optimizations from:

  • #3827

codeflash-ai[bot] avatar Apr 15 '25 16:04 codeflash-ai[bot]

This PR is now faster! πŸš€ codeflash-ai[bot] accepted my code suggestion above.

codeflash-ai[bot] avatar Apr 15 '25 16:04 codeflash-ai[bot]

@matclayton sorry, I totally missed that message! do you know if you can write a small reproduction? or was that fixed with GraphQL-core 3.3.0a7?

patrick91 avatar Apr 16 '25 08:04 patrick91

Sorry I didn't follow up here, this is fixed in GraphQL core 3.3.0va7, btw we're running GraphQL-core 3.3.0v7 in prod now, and not seen a single issue so far. Need to still confirm the other bits of this PR, I'm hoping the team can take it off me the next week or so.

matclayton avatar Apr 16 '25 09:04 matclayton

/pre-release

patrick91 avatar Apr 16 '25 09:04 patrick91

this doesn't render/print the directives (to double check)

patrick91 avatar Apr 29 '25 16:04 patrick91

worth checking if those directive should be printed, since we don't print directives like @include and @skip

patrick91 avatar Apr 29 '25 16:04 patrick91

/pre-release

patrick91 avatar May 07 '25 18:05 patrick91

/pre-release

patrick91 avatar May 10 '25 19:05 patrick91

We've managed to get this up and running in django 5.1, Channels and with granian in our codebase by the way. Works fantastically. We haven't managed to update the Relay Network to make use of it yet (we have quite a complex setup there) but its looking nice! @millar is who's been looking at it on our end.

matclayton avatar May 13 '25 15:05 matclayton

Just tried the pre release and i found a small bug: If you use MaskErrors it checks for result.errors. In a defer response this is nested in result.initial_result.errors

Speedy1991 avatar May 28 '25 14:05 Speedy1991

We've fully integrated this into our stack now without any issues!

millar avatar Jun 05 '25 09:06 millar

@millar @Speedy1991 thank you both! I'll see if I can update the extension!

Also, I think that would work only on the first response? I'll double check

patrick91 avatar Jun 05 '25 11:06 patrick91

@patrick91 maybe it would be better to fix https://github.com/strawberry-graphql/strawberry/issues/3844 first? This would avoid errors in other extensions too?

Speedy1991 avatar Jun 05 '25 12:06 Speedy1991

Any chance of another pre release of this with all the latest changes in please?

millar avatar Jun 23 '25 08:06 millar

/pre-release

patrick91 avatar Jun 23 '25 09:06 patrick91

⚑️ Codeflash found optimizations for this PR

πŸ“„ 12% (0.12x) speedup for process_result in strawberry/http/__init__.py

⏱️ Runtime : 100 microseconds β†’ 89.8 microseconds (best of 153 runs)

I created a new dependent PR with the suggested changes. Please review:

  • #3938

If you approve, it will be merged into this PR (branch feature/defer).

codeflash-ai[bot] avatar Jul 01 '25 11:07 codeflash-ai[bot]

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


This release adds experimental support for GraphQL's @defer and @stream directives, enabling incremental delivery of response data.

Note: this only works when using Strawberry with graphql-core>=3.3.0a9.

Features

  • @defer directive: Allows fields to be resolved asynchronously and delivered incrementally
  • @stream directive: Enables streaming of list fields using the new strawberry.Streamable type
  • strawberry.Streamable[T]: A new generic type for defining streamable fields that work with @stream

Configuration

To enable these experimental features, configure your schema with:

from strawberry.schema.config import StrawberryConfig

schema = strawberry.Schema(
    query=Query, config=StrawberryConfig(enable_experimental_incremental_execution=True)
)

Here's the tweet text:

πŸ†• Release (next) is out! Thanks to @patrick91 for the PR πŸ‘

This release adds experimental support for GraphQL's @defer and @stream directives, enabling incremental delivery of response data! πŸš€

Get it here πŸ‘‰ https://strawberry.rocks/release/(next)

botberry avatar Jul 16 '25 14:07 botberry

/pre-release

patrick91 avatar Jul 18 '25 09:07 patrick91

⚑️ Codeflash found optimizations for this PR

πŸ“„ 24% (0.24x) speedup for type in strawberry/experimental/pydantic/object_type.py

⏱️ Runtime : 182 microseconds β†’ 146 microseconds (best of 9 runs)

I created a new dependent PR with the suggested changes. Please review:

  • #3948

If you approve, it will be merged into this PR (branch feature/defer).

codeflash-ai[bot] avatar Jul 18 '25 16:07 codeflash-ai[bot]