-
-
Notifications
You must be signed in to change notification settings - Fork 533
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
Support subscriptions extensions #3554
Support subscriptions extensions #3554
Conversation
for more information, see https://pre-commit.ci
…ons' into support_extensions_on_subscriptions
for more information, see https://pre-commit.ci
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3554 +/- ##
==========================================
- Coverage 96.79% 96.77% -0.03%
==========================================
Files 517 521 +4
Lines 33517 33731 +214
Branches 5570 5627 +57
==========================================
+ Hits 32444 32644 +200
- Misses 845 855 +10
- Partials 228 232 +4 |
CodSpeed Performance ReportMerging #3554 will degrade performances by 80%Comparing Summary
Benchmarks breakdown
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just finished reviewing the websocket protocol parts. Looks like readability went up in some places and we still follow the protocol specs 🎉 I left some minor comments and my approval because at its core this PR is pretty much good to go.
if extensions is not None: | ||
assert response["payload"]["extensions"] == extensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be assert response["payload"]["extensions"] == extensions or {}
because extensions
, if set, would be a map? I would prefer including the or {}
, because that should technically be a valid value as well.
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last review round. After this one I'm ready to actually approve this
|
||
if self.directives: | ||
extensions.append(DirectivesExtensionSync if sync else DirectivesExtension) | ||
def _get_middleware_manager( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same suggestion as @patrick91 made applies for this. You can probably move this to a cached_property called middleware_manager
, then you don't need to handle caching by yourself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is kinda trickier because extensions
are not just a boolean argument. I think the current approuch suffice.
LMK if thats a blocker, I'll think of something better.
yield first | ||
async for result in asyncgen: | ||
yield result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering what happens if this gets cancelled, specially with asyncio.CancelledError
, maybe the asyncgen would not be closed?
Maybe:
yield first | |
async for result in asyncgen: | |
yield result | |
try: | |
yield first | |
async for result in asyncgen: | |
yield result | |
finally: | |
with suppress(RuntimeError): | |
await asyncgen.aclose() |
or to do that specifically for cancellederror:
yield first | |
async for result in asyncgen: | |
yield result | |
try: | |
yield first | |
async for result in asyncgen: | |
yield result | |
except asyncio.CancelledError: | |
with suppress(RuntimeError): | |
await asyncgen.aclose() | |
raise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you think of a cenario that this would happend? I'll add a test for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering what happens if this gets cancelled, specially with asyncio.CancelledError, maybe the asyncgen would not be closed?
I'm pretty sure the async generator would also live in the canceled task and, therefore, be automatically canceled as well.
# To overcome this while maintaining the extension contexts we do this trick. | ||
first = await asyncgen.__anext__() | ||
if isinstance(first, PreExecutionError): | ||
await asyncgen.aclose() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to raise RuntimeError
in case the generator is already closed
await asyncgen.aclose() | |
with suppress(RuntimeError): | |
await asyncgen.aclose() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would it be though?
if extensions is not None: | ||
assert response["payload"]["extensions"] == extensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand. how would that differ from what it does RN?
What you have right now will not check what response["payload"]["extensions"]
is at all
My suggestion (and I think @DoctorJohn is saying the same) is to also check it when extensions
is None
, to make sure that it contains an empty list/dict/whatever would be returned in that case
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's go!
Description
Support extensions in subscriptions operations.
Thing to note is that GraphQL core ~3.2 doesn't support middlewares (which are used by our schema extension
resolve
) on subscription. Support for this was added in 876aef6.We can initially remove
resolve
support until we upgrade to 3.3 Please LMK what you guys think.PS: There are few tests that are not passing since I upgraded GraphQL-core (for the midleware support) and there are some incompatibilities (mostly due to spaces in the SDL).
Types of Changes
Issues Fixed or Closed by This PR
fix #2097
closes #3598
TODO
TODO
's after review is accepted.Things I am not certain about
on_execute
was chosen the mitigate the normalon_execute
per subscription message.The thing is after the last yield of the subscription we still wrap with
on_execution
causing a last on_execution lifetime although the async gen raisedStopAsyncIteration
I am not sure this is the wanted behavior.ExecutionContext.extensions_result
on every yield do you think this is the wanted behaviour?Summary by Sourcery
This pull request introduces support for schema extensions in subscription operations, allowing hooks to be executed for each yield in a subscription. It also includes significant refactoring to improve error handling and performance, along with comprehensive tests and updated documentation.
on_execute
to be called for each subscription yield.