Skip to content

Bug: PydanticSerializer can't save results with UUID, datetime or Enum fields, so retries run the function again #8569

Closed

Description

@AtulJoshi1206

Expected Behaviour

When @idempotent_function uses output_serializer=PydanticSerializer and the function returns a Pydantic model with UUID, datetime, date, plain Enum, bytes or set fields, the result should be saved as a COMPLETED record. Later calls with the same payload should get the model back without running the function again.

The same model already works elsewhere:

It only fails with the serializer that's meant to give the model back.

Current Behaviour

PydanticSerializer.to_dict returns model_dump(), which is Python mode, so the dict still holds UUID, datetime and Enum objects. save_success then runs json.dumps(result, cls=Encoder, sort_keys=True). The Encoder only handles Decimal, Pydantic models and dataclasses, so it raises TypeError, and the decorator raises:

IdempotencyPersistenceLayerError: Failed to update record state to success in idempotency store - (Object of type datetime is not JSON serializable)

By then the function has already run, and the record stays INPROGRESS:

  • Retry before the in-progress expiry: it gets IdempotencyAlreadyInProgressError.
  • Retry after it (e.g. an async invocation retry or an SQS redelivery): the function runs again and fails the same way.

So a payment-style side effect can run once per retry. In a local run with a fake persistence layer and three retries after the in-progress window, the side effect ran 3 times.

Code snippet

import os
from datetime import datetime, timezone
from enum import Enum
from uuid import UUID, uuid4

from pydantic import BaseModel

from aws_lambda_powertools.utilities.idempotency import (
    DynamoDBPersistenceLayer,
    IdempotencyConfig,
    idempotent_function,
)
from aws_lambda_powertools.utilities.idempotency.serialization.pydantic import PydanticSerializer
from aws_lambda_powertools.utilities.typing import LambdaContext

persistence_layer = DynamoDBPersistenceLayer(table_name=os.environ["IDEMPOTENCY_TABLE"])
config = IdempotencyConfig(event_key_jmespath="order_id")


class PaymentStatus(Enum):
    CHARGED = "charged"


class Order(BaseModel):
    order_id: str
    amount: int


class Payment(BaseModel):
    payment_id: UUID
    charged_at: datetime
    status: PaymentStatus


@idempotent_function(
    data_keyword_argument="order",
    config=config,
    persistence_store=persistence_layer,
    output_serializer=PydanticSerializer,
)
def charge(order: Order) -> Payment:
    # call the payment provider here
    return Payment(payment_id=uuid4(), charged_at=datetime.now(timezone.utc), status=PaymentStatus.CHARGED)


def lambda_handler(event: dict, context: LambdaContext):
    config.register_lambda_context(context)
    payment = charge(order=Order(**event))
    return payment.model_dump(mode="json")

Possible Solution

Dump in JSON mode, as _prepare_data and shared.functions.pydantic_to_dict already do:

def to_dict(self, data: BaseModel) -> dict:
    return data.model_dump(mode="json")

from_dict uses model_validate, which parses the stored strings back into UUID, datetime, Enum and so on, so it doesn't need to change. For models that already work, the stored JSON stays the same. I have a fix with tests and will open a PR for it.

DataclassSerializer has the same gap, since asdict() keeps the raw objects. Its from_dict (Model(**data)) can't turn strings back into those types, though, so it needs its own fix.

Steps to Reproduce

The root cause, locally and without AWS:

import json
from datetime import datetime, timezone
from uuid import UUID, uuid4

from pydantic import BaseModel

from aws_lambda_powertools.shared.json_encoder import Encoder
from aws_lambda_powertools.utilities.idempotency.serialization.pydantic import PydanticSerializer


class Payment(BaseModel):
    payment_id: UUID
    charged_at: datetime


payment = Payment(payment_id=uuid4(), charged_at=datetime.now(timezone.utc))
json.dumps(PydanticSerializer(Payment).to_dict(payment), cls=Encoder, sort_keys=True)  # what save_success does
# TypeError: Object of type datetime is not JSON serializable

With the decorator, as in the snippet above, using any persistence layer:

  1. Call charge(order=...). The function body runs, then IdempotencyPersistenceLayerError is raised, and the record stays INPROGRESS.
  2. Call it again with the same order after the in-progress expiry. The function body runs again.

Powertools for AWS Lambda (Python) version

latest (3.36.0); every release since PydanticSerializer was added in v2.24.0 (#2951)

AWS Lambda function runtime

3.14 (reproduced locally on Python 3.14.4, Pydantic 2.13.5; not runtime-specific)

Packaging format used

PyPi

Debugging logs

Traceback (most recent call last):
  File ".../aws_lambda_powertools/utilities/idempotency/base.py", line 300, in _get_function_response
    self.persistence_store.save_success(data=self.data, result=serialized_response)
  File ".../aws_lambda_powertools/utilities/idempotency/persistence/base.py", line 302, in save_success
    response_data = json.dumps(result, cls=Encoder, sort_keys=True)
  ...
TypeError: Object of type datetime is not JSON serializable
when serializing dict item 'charged_at'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  ...
  File ".../aws_lambda_powertools/utilities/idempotency/base.py", line 302, in _get_function_response
    raise IdempotencyPersistenceLayerError(
aws_lambda_powertools.utilities.idempotency.exceptions.IdempotencyPersistenceLayerError: Failed to update record state to success in idempotency store - (Object of type datetime is not JSON serializable)

Activity

  1. powertools-for-aws-oss-automation commented on Oct 17, 2026

    @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.

  2. github-actions commented on Oct 22, 2026

    @github-actions
    Contributor

    This is now released under 3.37.0 version!

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

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions