Skip to content

fix(event_handler): normalize Union and RootModel sequences in body validation#8067

Merged
leandrodamascena merged 3 commits intoaws-powertools:developfrom
danjhd:fix/union-list-body-normalization
Mar 26, 2026
Merged

fix(event_handler): normalize Union and RootModel sequences in body validation#8067
leandrodamascena merged 3 commits intoaws-powertools:developfrom
danjhd:fix/union-list-body-normalization

Conversation

@danjhd
Copy link
Copy Markdown
Contributor

@danjhd danjhd commented Mar 24, 2026

Issue number: closes #8057

Summary

Changes

Fixed a bug in the OpenAPI validation middleware where body parameters with Union types containing sequences (e.g., Union[Model, List[Model]]) or RootModel[List[Model]] were incorrectly normalized. The middleware was extracting only the first element from list inputs instead of preserving the full list.

Key changes:

  • Added _is_or_contains_sequence() helper function to detect sequences within Union types and RootModel wrappers
  • Updated _normalize_field_value() to use the new helper, ensuring proper handling of complex type annotations
  • Added comprehensive test coverage for Union and RootModel sequence handling

User experience

Before:

When defining an endpoint with a body parameter like Union[Item, List[Item]] and sending a JSON array with multiple items:

@app.post("/items")
def handler(items: Annotated[Union[Item, List[Item]], Body()]):
    return {"count": len(items) if isinstance(items, list) else 1}

Sending [{"name": "item1"}, {"name": "item2"}, {"name": "item3"}] would incorrectly pass only the first item to the handler, causing data loss.

After:

The handler now correctly receives the full list when a list is sent, or a single item when a single object is sent. Both Union[Model, List[Model]] and RootModel[List[Model]] patterns work as expected, allowing flexible API designs that accept either single items or collections.


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

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

@pull-request-size pull-request-size bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Mar 24, 2026
@boring-cyborg
Copy link
Copy Markdown

boring-cyborg bot commented Mar 24, 2026

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

…n/RootModel

The original fix only checked one level deep. This makes
_is_or_contains_sequence recursive so it catches:
- Optional[RootModel[List[Model]]] (Union containing RootModel)
- RootModel[Union[Model, List[Model]]] (RootModel wrapping Union)

Adds 16 regression tests covering edge cases: Optional, empty list,
single-element list, pipe syntax, cross-resolver, large payloads, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@pull-request-size pull-request-size bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 26, 2026
@leandrodamascena leandrodamascena marked this pull request as ready for review March 26, 2026 13:49
@leandrodamascena leandrodamascena requested a review from a team as a code owner March 26, 2026 13:49
Copy link
Copy Markdown
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

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

Hey @danjhd thanks a lot for working on this. I fixed errors in mypy/ruff and asked Claude to add more tests.

Everything is fine to merge now.

APPROVED!

@sonarqubecloud
Copy link
Copy Markdown

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.64%. Comparing base (2ab8f39) to head (8de34a2).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
...ls/event_handler/middlewares/openapi_validation.py 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #8067      +/-   ##
===========================================
- Coverage    96.65%   96.64%   -0.01%     
===========================================
  Files          282      282              
  Lines        13775    13790      +15     
  Branches      1096     1102       +6     
===========================================
+ Hits         13314    13328      +14     
  Misses         339      339              
- Partials       122      123       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leandrodamascena leandrodamascena changed the title fix(event-handler): normalize Union and RootModel sequences in body validation fix(event_handler): normalize Union and RootModel sequences in body validation Mar 26, 2026
@leandrodamascena leandrodamascena merged commit 3137ecb into aws-powertools:develop Mar 26, 2026
13 of 14 checks passed
@boring-cyborg
Copy link
Copy Markdown

boring-cyborg bot commented Mar 26, 2026

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

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

Labels

event_handlers size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Union[Model, List[Model]] and RootModel[List[Model]] with Body() only receive first element since v3.21.0

3 participants