Repository navigation
BUG: Dependency Injection with Pydantic Validation #8333
Copy link
Copy link
Closed
Labels
Description
Discussed in #8330
Originally posted by bdellegrazie July 11, 2026
Hi,
I'm trying to use the Dependency injection feature with Pydantic validation on routes and it fails.
Does anyone have a simple example where the injected dependency is not validated by Pydantic and isn't part of the Open API signature?
Non-working Example:
import botocore
import boto3
from typing import Annotated, Any
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayHttpResolver
from aws_lambda_powertools.event_handler.depends import Depends
from aws_lambda_powertools.event_handler.exceptions import (
NotFoundError,
)
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext
from boto3.dynamodb.types import TypeDeserializer
from pydantic import BaseModel, Field
from types_boto3_dynamodb.client import DynamoDBClient
from types_boto3_dynamodb.type_defs import AttributeValueTypeDef
logger = Logger()
tracer = Tracer()
app: APIGatewayHttpResolver = APIGatewayHttpResolver(
enable_validation=True,
)
# A simple model
class Example(BaseModel):
id: Annotated[str, Field(..., description="ID for this object.")]
info: Annotated[str, Field(..., description="Additional Data")]
def dynamo_to_python(dynamo_object: dict[str, AttributeValueTypeDef]) -> dict[str, Any]:
deserializer = TypeDeserializer()
return {k: deserializer.deserialize(v) for k, v in dynamo_object.items()}
def get_botocore_session() -> botocore.session.Session:
return botocore.session.get_session()
def get_boto3_session(
botocore_session: Annotated[
botocore.session.Session,
Depends(get_botocore_session),
],
) -> boto3.Session:
return boto3.Session(botocore_session=botocore_session)
def get_dynamodb_client(
boto3_session: Annotated[
boto3.Session,
Depends(get_boto3_session),
],
) -> DynamoDBClient:
return boto3_session.client("dynamodb")
@app.get("/example/<id>")
@tracer.capture_method(capture_response=False)
def example(
id: str,
dynamodb_client: Annotated[
DynamoDBClient,
Depends(get_dynamodb_client),
],
) -> Example:
result = dynamodb_client.get_item(
TableName="example-table",
Key={
"id": {"S": id},
},
)
if "Item" not in result:
raise NotFoundError
return Example.model_validate(dynamo_to_python(result["Item"]))
@logger.inject_lambda_context(
correlation_id_path=correlation_paths.API_GATEWAY_HTTP, clear_state=True
)
@tracer.capture_lambda_handler
def lambda_handler(
event: dict,
context: LambdaContext,
) -> dict:
return app.resolve(event, context)
Simple pytest test class:
@pytest.fixture
def example_table(dynamodb: DynamoDBClient) -> str:
# Create the example table
dynamodb.create_table(
TableName="example-table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
return "example-table"
Fixtures:
import os
import json
import time
import uuid
import botocore
import boto3
import pytest
from typing import Final
from aws_lambda_powertools import Logger
from moto import mock_aws
from types_boto3_dynamodb import DynamoDBClient
MOTO_ACCOUNT_ID: Final[str] = "123456789012"
logger = Logger(child=True)
@pytest.fixture(scope="function")
def aws_credentials() -> None:
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SECURITY_TOKEN"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"
# as an additional layer of protection against accidentally touching our real AWS environment
os.environ["MOTO_ACCOUNT_ID"] = MOTO_ACCOUNT_ID
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
# Framework specific
os.environ["POWERTOOLS_DEV"] = "true"
@pytest.fixture(scope="function")
def mocked_aws(aws_credentials: None):
with mock_aws():
yield
@pytest.fixture(scope="function")
def botocore_session(aws_credentials: None) -> botocore.session.Session: # type: ignore
with mock_aws():
yield botocore.session.get_session()
@pytest.fixture(scope="function")
def boto3_session(botocore_session: botocore.session.Session) -> boto3.Session: # type: ignore
with mock_aws():
yield boto3.Session(botocore_session=botocore_session)
@pytest.fixture(scope="function")
def dynamodb(boto3_session: boto3.Session) -> DynamoDBClient: # type: ignore
with mock_aws():
yield boto3_session.client("dynamodb")
@pytest.fixture
def example_table(dynamodb: DynamoDBClient) -> str:
# Create the example table
dynamodb.create_table(
TableName="example-table",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
return "example-table"
@pytest.fixture
def example_item(dynamodb: DynamoDBClient, example_table: str) -> str:
# Create the example table
dynamodb.put_item(
TableName="example-table",
Item={
"id": {"S": "example-id"},
"info": {"S": "example-info"},
},
)
return "example-id"
Results in:
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for <class 'botocore.session.Session'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.
I have tried to:
- add
arbitrary_types_allowed=Trueto the route but it doesn't work. - use SkipValidation on the
dynamodb_clientbut that doesn't work either.
Any ideas?
Thanks,
Brett
Reacted by Brett Delle Grazie
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsComing soon
Activity
bdellegrazie commentedon Jul 11, 2026
Thank you @leandrodamascena for the insanely fast response in triage and work-around.
Related side question: the intent was to ensure that low-level AWS clients are created as efficiently as possible during Lambda startup before processing begins while still remaining testable.
This is especially important in the context of asynchronous I/O, which this example does not use.
If there are better approaches I'd love to hear them / be pointed in the general direction.
Obviously this may obscure the issue / PR with unrelated material, so I'm happy to open a discussion if that's the best course of action.
Thanks in advance!
leandrodamascena commentedon Jul 12, 2026
Hey @bdellegrazie, thanks for the kind words and I'm glad the workaround unblocked you!
So, the "create the clients efficiently but keep it testable" thing is actually one of my favorite parts of
Depends, because you get both for free once you set it up right.What I do is build the client once at import time (so the cold start pays for it, not every request) and let the dependency just return it:
Then for the tests you never touch the real client and you just override the dependency:
That's literally what
dependency_overridesis there for. Real client in prod, fake in tests, and the handler has no idea which one it got. This is the part I always tell people about because it's easy to miss.Now the async bit is where it gets annoying, and I'll be honest with you: boto3 is sync, so the second you want real concurrent I/O you're either throwing calls into a thread pool or jumping to aioboto3 and babysitting the aiobotocore session yourself (create it at startup, never per-invoke, since it's holding connections).
If the async I/O is really the thing you care about with DynamoDB, take a look at pydynox. It's async-first with a Rust core, so the network calls are genuinely non-blocking instead of going through sync boto3, and it plays nice with Pydantic if you're already using models. Full disclosure so nobody thinks I'm being sneaky: it's not a Powertools thing, I just think it fits exactly the problem you're describing with async performance.
Btw, if you want to dig into the whole "how should async AWS clients be structured in Lambda" topic, let's take it to a discussion so it doesn't get lost under this bug. Happy to go back and forth there.
Thanks
powertools-for-aws-oss-automation commentedon Jul 13, 2026
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.
github-actions commentedon Jul 13, 2026
This is now released under 3.31.1 version!
bdellegrazie commentedon Jul 13, 2026
Thanks @leandrodamascena !