Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let WS and HTTP use the same JSON encoder #3708

Conversation

DoctorJohn
Copy link
Member

@DoctorJohn DoctorJohn commented Nov 20, 2024

Description

It was already possible and documented to override encode_json on views to customize the JSON encoder used for responses sent via HTTP. The same encode_json method is now also used to encode WebSocket messages.

This was proposed a few weeks ago by @bellini666 and it's super handy if someone wants to use orjson or otherwise customize JSON responses and messages.

Note that this change is kinda breaking since the encode_json methode previously only expected GraphQLHTTPResponse dicts, whereas now it must be able to handle any kind of json data.
In reality anyone overriding this method can probably already handle any json data.

(Will do the same for decoding in a separate PR).

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Summary by Sourcery

Unify the JSON encoding process for HTTP and WebSocket by using the same encode_json method, allowing for consistent customization across both protocols. Update documentation and add tests to support this change.

New Features:

  • Enable the use of a single JSON encoder for both HTTP responses and WebSocket messages by allowing the override of the encode_json method.

Documentation:

  • Update documentation to reflect the change in the encode_json method, which now applies to both HTTP and WebSocket JSON responses.
  • Add a breaking changes document for version 0.251.0, detailing the changes to the encode_json method signature.

Tests:

  • Add a test to verify that WebSocket handlers use the view's encode_json method for encoding JSON messages.

Copy link
Contributor

sourcery-ai bot commented Nov 20, 2024

Reviewer's Guide by Sourcery

This PR unifies JSON encoding across HTTP and WebSocket responses by making the encode_json method handle both types of responses. The implementation modifies the WebSocket adapters to use the view's encode_json method instead of direct JSON encoding, and updates the method signature to accept any JSON-serializable object rather than just GraphQLHTTPResponse types.

Sequence diagram for WebSocket message encoding

sequenceDiagram
    participant Client
    participant WebSocketAdapter
    participant AsyncBaseHTTPView
    participant WebSocket

    Client->>WebSocketAdapter: Send message
    WebSocketAdapter->>AsyncBaseHTTPView: encode_json(message)
    AsyncBaseHTTPView-->>WebSocketAdapter: Encoded JSON
    WebSocketAdapter->>WebSocket: send_text(encoded JSON)
    WebSocket-->>Client: Acknowledge receipt
Loading

Updated class diagram for AsyncWebSocketAdapter

classDiagram
    class AsyncWebSocketAdapter {
        +AsyncBaseHTTPView view
        +iter_json(ignore_parsing_errors: bool)
        +send_json(message: Mapping[str, object])
        +close(code: int, reason: str)
    }
    class AsyncBaseHTTPView {
        +encode_json(data: object) : str
    }
    AsyncWebSocketAdapter --> AsyncBaseHTTPView: uses
    note for AsyncWebSocketAdapter "Now uses view's encode_json method for JSON encoding"
Loading

File-Level Changes

Change Details Files
Modified WebSocket adapters to use view's encode_json method
  • Added view reference to WebSocket adapter constructors
  • Updated send_json methods to use view's encode_json instead of direct JSON encoding
  • Modified adapter initialization to pass view instance
strawberry/aiohttp/views.py
strawberry/asgi/__init__.py
strawberry/litestar/controller.py
strawberry/http/async_base_view.py
strawberry/channels/handlers/ws_handler.py
Updated encode_json method signature and documentation
  • Changed parameter type from GraphQLHTTPResponse to object
  • Updated method description to mention both HTTP and WebSocket support
  • Added breaking change documentation
strawberry/http/base.py
docs/breaking-changes/0.251.0.md
docs/integrations/fastapi.md
docs/integrations/aiohttp.md
docs/integrations/asgi.md
docs/integrations/django.md
docs/integrations/flask.md
docs/integrations/quart.md
docs/integrations/sanic.md
docs/integrations/chalice.md
Added test coverage for custom JSON encoding in WebSocket handlers
  • Created test to verify custom encode_json usage in WebSocket communication
tests/websockets/test_websockets.py

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.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a 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. You can also use
    this command to specify where the summary should be inserted.

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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @DoctorJohn - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 2 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

strawberry/http/base.py Show resolved Hide resolved
docs/integrations/flask.md Outdated Show resolved Hide resolved
@botberry
Copy link
Member

botberry commented Nov 20, 2024

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


Starting with this release, the same JSON encoder is used to encode HTTP
responses and WebSocket messages.

This enables developers to override the encode_json method on their views to
customize the JSON encoder used by all web protocols.

Here's the tweet text:

🆕 Release (next) is out! Thanks to @NucleonJohn for the PR 👏

Get it here 👉 https://strawberry.rocks/release/(next)

@DoctorJohn DoctorJohn force-pushed the let-websockets-access-custom-json-encoder branch from af00c2d to d8917a5 Compare November 20, 2024 21:29
Copy link

codecov bot commented Nov 20, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.01%. Comparing base (4551c04) to head (d8917a5).
Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3708      +/-   ##
==========================================
+ Coverage   96.71%   97.01%   +0.30%     
==========================================
  Files         500      500              
  Lines       33436    33454      +18     
  Branches     5590     5591       +1     
==========================================
+ Hits        32336    32456     +120     
+ Misses        883      794      -89     
+ Partials      217      204      -13     
---- 🚨 Try these New Features:

Copy link

codspeed-hq bot commented Nov 20, 2024

CodSpeed Performance Report

Merging #3708 will not alter performance

Comparing DoctorJohn:let-websockets-access-custom-json-encoder (d8917a5) with main (069fe2c)

Summary

✅ 15 untouched benchmarks

your method can handle the same inputs as the built-in `json.dumps` method:

```python
def encode_json(self, data: object) -> str: ...
Copy link
Member

Choose a reason for hiding this comment

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

could we type this better? is it worth it?

Copy link
Member Author

@DoctorJohn DoctorJohn Nov 20, 2024

Choose a reason for hiding this comment

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

json.loads expects Any so I went with object. Technically it's anything that can be serialized to JSON (list, dict, int, float, str, etc.). But the json library doesn't provide a type for it which we could reuse. We would have to create our own, pretty sure it's not worth it for this method.

Copy link
Member

Choose a reason for hiding this comment

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

@DoctorJohn mmh, is it because the ws integration can send pretty much anything, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

it because the ws integration can send pretty much anything

Not necessarily, the WS messages are all quite well defined. If we wanted to limited the input to the encode_json method we could go with Union[GraphQLHTTPResponse, Message, OperationMessage].

However, this signature would signal to users that they should implement a method that can specifically/only encode these messages. If someone does that, they would have to update their encode_json method every time we add a new protocol or any of the specs changes.

IMHO we should ask the user to write encode_json methods that can encode any JSON payload and not just a subset, so that they don't worry about specific payloads and so that we can use that method for any JSON that needs to be encoded from within views.

Copy link
Member

Choose a reason for hiding this comment

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

ok, sounds good to me 😊

@patrick91 patrick91 merged commit 67eb7ab into strawberry-graphql:main Nov 21, 2024
109 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants