Skip to content

Handle unknown event types gracefully instead of crashing#680

Open
Alan4506 wants to merge 1 commit intosmithy-lang:developfrom
Alan4506:fix/unknown-event-type-handling
Open

Handle unknown event types gracefully instead of crashing#680
Alan4506 wants to merge 1 commit intosmithy-lang:developfrom
Alan4506:fix/unknown-event-type-handling

Conversation

@Alan4506
Copy link
Copy Markdown
Contributor

@Alan4506 Alan4506 commented Apr 7, 2026

Background

The Smithy spec (streaming.html#client-behavior) states:

"Adding new events to an event stream union is considered a backward compatible change; clients SHOULD NOT fail when an unknown event is received."

Currently, in smithy-aws-event-stream deserializers.py, EventDeserializer.read_struct() does a hard dictionary lookup (schema.members[member_name]) which raises KeyError when the service sends an event type not present in the generated model. So we have to add some logic to handle unknown event types.

Additionally, although smithy-python automatically creates unknown variants for streams, these variants only have a tag field unlike normal events that have a value field. When AWSEventReceiver.receive() calls getattr(result, "value") without a default, it raises AttributeError for unknown variants.

These were discovered during Q Business Chat streaming integration testing. The service sends intermediateGroupEvent and intermediateMessageEvent events not in any published SDK model, causing the async for event in output_stream loop to crash.

Reference implementations in other Smithy SDKs:

Both smithy-java and smithy-rs (Rust) already handle unknown event types gracefully:

  • smithy-java (TestEventStream.java): Generates a $Unknown(String memberName) record for each event stream union. The builder has an $unknownMember(String memberName) method that the deserializer calls when it encounters an unrecognized event type. The unknown variant preserves the event type name.

  • smithy-rs (EventStreamUnmarshallerGenerator.kt): The generated unmarshaller has an _unknown_variant match arm that returns Ok(UnmarshalledMessage::Event(Output::Unknown)) for client targets. Unlike Java, the Rust unknown variant does not preserve the event type name.

This PR follows the smithy-java approach of preserving the event type name in the unknown variant's tag field, so users can identify which unmodeled event was received (e.g., ChatOutputStreamUnknown(tag="intermediateGroupEvent")).

Description of Changes

  1. smithy-aws-event-stream (deserializers.py): When member_name is not in schema.members, construct a synthetic Schema with member_index=-1 and call the consumer, which triggers the generated case _: branch in UnionGenerator.java.
  2. smithy-python codegen (UnionGenerator.java): The case _: branch (previously just logging) now also constructs the per-union unknown variant with the event type name as tag.
  3. smithy-aws-event-stream (aio/__init__.py): Change getattr(result, "value") to getattr(result, "value", None) to prevent AttributeError on unknown variants.

Testing

Regenerated all existing clients locally and all their integration tests passed. Also, verified with Q Business Chat streaming — unknown events (intermediateGroupEvent, intermediateMessageEvent) are now handled gracefully instead of crashing. Users can identify unknown events via isinstance and access the event type name:

from aws_sdk_qbusiness.models import ChatOutputStreamUnknown

async for event in output_stream:
    if isinstance(event, ChatOutputStreamUnknown):
        print(f"Unknown event: {event.tag}")

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Alan4506 Alan4506 requested a review from a team as a code owner April 7, 2026 16:01
@Alan4506 Alan4506 changed the title Fix EvenHandle unknown event types gracefully instead of crashing Handle unknown event types gracefully instead of crashing Apr 7, 2026
logger.debug(
"Unknown event type: %s", member_name
)
from smithy_core.shapes import ShapeID
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we move the ShapeID import to the top with the other smithy_core.shapes imports? Doesn't seem like we need it inline here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should add unit tests in test_deserializers.py for the unknown event handling path.

Also, the test fixture EventStreamDeserializer._consumer still raises SmithyError in the default case. Should that be updated to match the new behavior?

result = self._deserializer(deserializer)
logger.debug("Successfully deserialized event: %s", result)
if isinstance(getattr(result, "value"), Exception):
if isinstance(getattr(result, "value", None), Exception):
Copy link
Copy Markdown
Contributor

@alexgromero alexgromero Apr 7, 2026

Choose a reason for hiding this comment

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

Suggested change
if isinstance(getattr(result, "value", None), Exception):
value = getattr(result, "value", None)
if isinstance(value, Exception):

Small readability suggestion (non-blocking): since only some variants have value, maybe assign it first and check that instead so it's a bit clearer and avoids looking up value twice here and in the raise statement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants