Skip to content

Commit 058f95c

Browse files
committed
pr feedback
1 parent a805f5e commit 058f95c

2 files changed

Lines changed: 56 additions & 54 deletions

File tree

‎gql/transport/aiohttp.py‎

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -540,73 +540,75 @@ async def _parse_multipart_part(
540540
:param part: aiohttp BodyPartReader for the part
541541
:return: ExecutionResult or None if part is empty/heartbeat
542542
"""
543-
# Verify the part has the correct content type
543+
try:
544+
body = await part.text()
545+
body = body.strip()
546+
except UnicodeDecodeError as e:
547+
log.warning(f"Failed to decode part: {ascii(e)}")
548+
return None
549+
544550
content_type = part.headers.get(aiohttp.hdrs.CONTENT_TYPE, "")
545-
if not content_type:
546-
log.debug("Skipping part with no content-type (likely heartbeat)")
551+
552+
if not content_type and not body:
553+
log.debug("Skipping part with no content-type and no body")
547554
return None
555+
556+
# Verify the part has the correct content type
548557
if not content_type.startswith("application/json"):
549558
raise TransportProtocolError(
550559
f"Unexpected part content-type: {content_type}. "
551560
"Expected 'application/json'."
552561
)
553562

554-
try:
555-
# Read the part content as text
556-
body = await part.text()
557-
body = body.strip()
558-
559-
if log.isEnabledFor(logging.DEBUG):
560-
log.debug("<<< %s", ascii(body or "(empty body, skipping)"))
563+
if log.isEnabledFor(logging.DEBUG):
564+
log.debug("<<< %s", ascii(body or "(empty body, skipping)"))
561565

562-
if not body:
563-
return None
566+
if not body:
567+
return None
564568

565-
# Parse JSON body using custom deserializer
569+
try:
566570
data = self.json_deserialize(body)
567-
568-
# Handle heartbeats - empty JSON objects
569-
if not data:
570-
log.debug("Received heartbeat, ignoring")
571-
return None
572-
573-
# The multipart subscription protocol wraps data in a "payload" property
574-
if "payload" not in data:
575-
log.warning("Invalid response: missing 'payload' field")
576-
return None
577-
578-
payload = data["payload"]
579-
580-
# Check for transport-level errors (payload is null)
581-
if payload is None:
582-
# If there are errors, this is a transport-level error
583-
errors = data.get("errors")
584-
if errors:
585-
error_messages = [
586-
error.get("message", "Unknown transport error")
587-
for error in errors
588-
]
589-
590-
for message in error_messages:
591-
log.error(f"Transport error: {message}")
592-
593-
raise TransportServerError("\n\n".join(error_messages))
594-
else:
595-
# Null payload without errors - just skip this part
596-
return None
597-
598-
# Extract GraphQL data from payload
599-
return ExecutionResult(
600-
data=payload.get("data"),
601-
errors=payload.get("errors"),
602-
extensions=payload.get("extensions"),
603-
)
604571
except json.JSONDecodeError as e:
605572
log.warning(
606573
f"Failed to parse JSON: {ascii(e)}, "
607574
f"body: {ascii(body[:100]) if body else ''}"
608575
)
609576
return None
610-
except UnicodeDecodeError as e:
611-
log.warning(f"Failed to decode part: {ascii(e)}")
577+
578+
# Handle heartbeats - empty JSON objects
579+
if not data:
580+
log.debug("Received heartbeat, ignoring")
581+
return None
582+
583+
# The multipart subscription protocol wraps data in a "payload" property
584+
if "payload" not in data:
585+
log.warning("Invalid response: missing 'payload' field")
612586
return None
587+
588+
payload = data["payload"]
589+
590+
# Check for transport-level errors (payload is null)
591+
if payload is None:
592+
# If there are errors, this is a transport-level error
593+
errors = data.get("errors")
594+
if errors:
595+
error_messages = [
596+
error.get("message", "Unknown transport error")
597+
for error in errors
598+
]
599+
600+
for message in error_messages:
601+
log.error(f"Transport error: {message}")
602+
603+
raise TransportServerError("\n\n".join(error_messages))
604+
else:
605+
# Null payload without errors - just skip this part
606+
return None
607+
608+
# Extract GraphQL data from payload
609+
return ExecutionResult(
610+
data=payload.get("data"),
611+
errors=payload.get("errors"),
612+
extensions=payload.get("extensions"),
613+
)
614+

‎tests/test_aiohttp_multipart.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,8 +481,8 @@ async def test_aiohttp_multipart_wrong_part_content_type(multipart_server):
481481

482482

483483
@pytest.mark.asyncio
484-
async def test_aiohttp_multipart_empty_content_type_skipped(multipart_server):
485-
"""Test that parts with empty/missing content-type are skipped as heartbeats."""
484+
async def test_aiohttp_multipart_empty_part_no_content_type_skipped(multipart_server):
485+
"""Test that empty parts with no content-type are skipped."""
486486
from gql.transport.aiohttp import AIOHTTPTransport
487487

488488
book1_payload = json.dumps({"payload": {"data": {"book": book1}}})

0 commit comments

Comments
 (0)