Skip to content

Bug: Incomplete validation of annotated parameter types (3.21.0 regression) #7600

Closed

Description

@rtandy

Expected Behaviour

Annotated parameter types using annotated_types should be validated by Pydantic. This used to work, but regressed starting from 3.21.0. Types are still validated, but constraint annotations are not.

I thought #7227 would have enabled using Pydantic Field annotations for this use case, and tried to switch to that, but that also doesn't work (in any Powertools version). Should I report this separately (perhaps as a feature request)?

Current Behaviour

Parameter types are validated, but constraints expressed using annotated_types are not enforced. Handler functions can be called with invalid parameter values.

Code snippet

from http import HTTPStatus
from typing import Annotated

from aws_lambda_powertools.event_handler import APIGatewayHttpResolver
from aws_lambda_powertools.event_handler.openapi.params import Body, Query

import pytest

# aws-lambda-powertools 3.20.0: ok
# aws-lambda-powertools 3.21.0 and 3.22.0: fail (value outside of range allowed)
from annotated_types import Interval
ConstrainedInt = Annotated[int, Interval(ge=0, le=100)]

# aws-lambda-powertools 3.20.0 and 3.21.0 and 3.22.0: error (Only one FieldInfo can be used per parameter)
#from pydantic.fields import Field
#ConstrainedInt = Annotated[int, Field(ge=0, le=100)]

app = APIGatewayHttpResolver(enable_validation=True)

@app.post('/body')
def handle_body(value: Annotated[ConstrainedInt, Body()]) -> None:
    assert isinstance(value, int)
    assert value >= 0
    assert value <= 100

@app.post('/query')
def handle_body(value: Annotated[ConstrainedInt, Query()]) -> None:
    assert isinstance(value, int)
    assert value >= 0
    assert value <= 100

def handler(event, context):
    return app.resolve(event, context)

params = [
    (0, HTTPStatus.OK),
    (50, HTTPStatus.OK),
    (100, HTTPStatus.OK),
    (-1, HTTPStatus.UNPROCESSABLE_ENTITY),
    (101, HTTPStatus.UNPROCESSABLE_ENTITY),
    ('not an int', HTTPStatus.UNPROCESSABLE_ENTITY),
]

@pytest.mark.parametrize('value,expected_status', params)
def test_handle_body(value, expected_status):
    event = {
        'rawPath': '/body',
        'requestContext': {
            'http': {
                'method': 'POST'
            },
            'stage': '$default'
        },
        'body': str(value)
    }
    response = handler(event, object())
    assert expected_status == response['statusCode']

@pytest.mark.parametrize('value,expected_status', params)
def test_handle_query(value, expected_status):
    event = {
        'rawPath': '/query',
        'requestContext': {
            'http': {
                'method': 'POST'
            },
            'stage': '$default'
        },
        'queryStringParameters': {
            'value': str(value)
        }
    }
    response = handler(event, object())
    assert expected_status == response['statusCode']

Possible Solution

No response

Steps to Reproduce

Run code snippet: pytest test_annotated_types.py

Powertools for AWS Lambda (Python) version

3.21.0, 3.22.0

AWS Lambda function runtime

3.11

Packaging format used

Lambda Layers

Debugging logs

Activity

  1. added
    bugSomething isn't working
    triagePending triage from maintainers
    on Oct 30, 2025
  2. leandrodamascena commented on Oct 30, 2025

    @leandrodamascena
    Contributor

    Hey @rtandy thanks for reporting this, I will take a look.

  3. leandrodamascena commented on Oct 30, 2025

    @leandrodamascena
    Contributor

    Hi @rtandy, can you confirm if this code (exactly as you pasted it here) works with Powertools <= 3.20.0? Without checking the code and just running your script, it fails in any version of Powertools. I'll check it in more detail, but I just wanted to confirm before dive deep into it.

  4. rtandy commented on Oct 30, 2025

    @rtandy
    Author

    Hi @leandrodamascena, thanks for responding so quickly. Confirmed, copy-pasted the exact code snippet above. Environment is macOS Sequoia 15.7.1, Python 3.11.14, Powertools 3.20.0, Pydantic 2.11.10.

    $ python3.11 -m venv venv
    $ venv/bin/pip3 install aws-lambda-powertools==3.20.0 
    Collecting aws-lambda-powertools==3.20.0
      Using cached aws_lambda_powertools-3.20.0-py3-none-any.whl.metadata (11 kB)
    Collecting jmespath<2.0.0,>=1.0.1 (from aws-lambda-powertools==3.20.0)
      Using cached jmespath-1.0.1-py3-none-any.whl.metadata (7.6 kB)
    Collecting typing-extensions<5.0.0,>=4.11.0 (from aws-lambda-powertools==3.20.0)
      Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
    Using cached aws_lambda_powertools-3.20.0-py3-none-any.whl (842 kB)
    Using cached jmespath-1.0.1-py3-none-any.whl (20 kB)
    Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB)
    Installing collected packages: typing-extensions, jmespath, aws-lambda-powertools
    Successfully installed aws-lambda-powertools-3.20.0 jmespath-1.0.1 typing-extensions-4.15.0
    $ venv/bin/pip3 install 'pydantic<2.12'              
    Collecting pydantic<2.12
      Using cached pydantic-2.11.10-py3-none-any.whl.metadata (68 kB)
    Collecting annotated-types>=0.6.0 (from pydantic<2.12)
      Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
    Collecting pydantic-core==2.33.2 (from pydantic<2.12)
      Using cached pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl.metadata (6.8 kB)
    Requirement already satisfied: typing-extensions>=4.12.2 in ./venv/lib/python3.11/site-packages (from pydantic<2.12) (4.15.0)
    Collecting typing-inspection>=0.4.0 (from pydantic<2.12)
      Using cached typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB)
    Using cached pydantic-2.11.10-py3-none-any.whl (444 kB)
    Using cached pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl (1.9 MB)
    Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
    Using cached typing_inspection-0.4.2-py3-none-any.whl (14 kB)
    Installing collected packages: typing-inspection, pydantic-core, annotated-types, pydantic
    Successfully installed annotated-types-0.7.0 pydantic-2.11.10 pydantic-core-2.33.2 typing-inspection-0.4.2
    $ venv/bin/pip3 install pytest         
    Collecting pytest
      Using cached pytest-8.4.2-py3-none-any.whl.metadata (7.7 kB)
    Collecting iniconfig>=1 (from pytest)
      Using cached iniconfig-2.3.0-py3-none-any.whl.metadata (2.5 kB)
    Collecting packaging>=20 (from pytest)
      Using cached packaging-25.0-py3-none-any.whl.metadata (3.3 kB)
    Collecting pluggy<2,>=1.5 (from pytest)
      Using cached pluggy-1.6.0-py3-none-any.whl.metadata (4.8 kB)
    Collecting pygments>=2.7.2 (from pytest)
      Using cached pygments-2.19.2-py3-none-any.whl.metadata (2.5 kB)
    Using cached pytest-8.4.2-py3-none-any.whl (365 kB)
    Using cached pluggy-1.6.0-py3-none-any.whl (20 kB)
    Using cached iniconfig-2.3.0-py3-none-any.whl (7.5 kB)
    Using cached packaging-25.0-py3-none-any.whl (66 kB)
    Using cached pygments-2.19.2-py3-none-any.whl (1.2 MB)
    Installing collected packages: pygments, pluggy, packaging, iniconfig, pytest
    Successfully installed iniconfig-2.3.0 packaging-25.0 pluggy-1.6.0 pygments-2.19.2 pytest-8.4.2
    $ pbpaste > test_annotated_types.py                  
    $ venv/bin/pytest test_annotated_types.py
    ===================================== test session starts =====================================
    platform darwin -- Python 3.11.14, pytest-8.4.2, pluggy-1.6.0
    rootdir: /Users/ryan
    collected 12 items                                                                            
    
    test_annotated_types.py ............                                                    [100%]
    
    ===================================== 12 passed in 0.27s ======================================
    

    And confirmed it fails with 3.21.0 and 3.22.0 with Pydantic 2.11 or 2.12.

  5. leandrodamascena commented on Oct 30, 2025

    @leandrodamascena
    Contributor

    Ohh thanks for confirming.. I was using Pydantic 2.12+ and I think this is related with this: #7552.

    Regarding the bug you opened, I think this is related with PR https://github.com/aws-powertools/powertools-lambda-python/pull/7227/files not checking the constraints. I'll take a look into it.

  6. leandrodamascena commented on Oct 31, 2025

    @leandrodamascena
    Contributor

    Thanks for reporting this @rtandy ! I've identified and fixed the regression.

    The problem was introduced in PR #7227 when we switched from FieldInfo.from_annotation() to using copy() in the copy_field_info() function. While this change correctly preserved discriminator functionality, the copy doesn't copy the annotated_types constraint metadata (like Interval) during the field copying process.

    When you use Annotated[int, Interval(ge=0, le=100), Body()], the annotation contains multiple layers of metadata. The old implementation was doing a simple shallow copy and directly assigning the new annotation, which meant nested constraint metadata from annotated_types was being discarded.

    The fix recursively extracts all constraint metadata from nested Annotated types while filtering out FieldInfo instances. This way, we preserve both the discriminator functionality from PR #7227 and the annotated_types constraints that were working before.

    I've also added tests to ensure Interval constraints work correctly with both Body and Query parameters, covering valid values, boundary conditions, and validation failures.

    I'll be sending a PR soon and launching it by Wednesday the 5th. Is it okay for you to wait until then?

  7. rtandy commented on Oct 31, 2025

    @rtandy
    Author

    I'll be sending a PR soon and launching it by Wednesday the 5th. Is it okay for you to wait until then?

    No rush at all - no problem to stay pinned to the older version for a while. Thank you very much for handling it so quickly!

  8. leandrodamascena commented on Oct 31, 2025

    @leandrodamascena
    Contributor

    Hey @rtandy! I was wondering if you can install Powertools from #7609 and run your tests to see if this fix the problem.

    Thanks

  9. rtandy commented on Oct 31, 2025

    @rtandy
    Author

    Hi @leandrodamascena, your branch passed all my tests for things that used to work before 3.21. (Noting that Pydantic Field annotations still don't work; but they never did before either, so no change.) Thank you!

  10. powertools-for-aws-oss-automation commented on Nov 3, 2025

    @powertools-for-aws-oss-automation

    Warning

    This issue is now closed. Please be mindful that future comments are hard for our team to see.
    If you need more assistance, please either reopen the issue, or open a new issue referencing this one.
    If you wish to keep having a conversation with other community members under this issue feel free to do so.

  11. github-actions commented on Nov 5, 2025

    @github-actions
    Contributor

    This is now released under 3.22.1 version!

  12. removed
    pending-releaseFix or implementation already in dev waiting to be released
    on Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions