aws-pdk icon indicating copy to clipboard operation
aws-pdk copied to clipboard

[BUG] (type-safe-api) JSON Date serialization fails for Python handlers

Open athewsey opened this issue 8 months ago • 2 comments

Describe the bug

Returning date fields (e.g. with format: date in OpenAPI spec) generates Python runtime code which throws errors because date/datetime objects are not serializable via standard Pydantic model_dump() + json.dumps().

There seems to be a TODO to switch to Pydantic model_dump_json() - which might fix the issue, but I'm not sure if it'd require solving other problems?

Expected Behavior

Given an API which returns a date field in a JSON schema, e.g:

  /my-cool-api:
    get:
      operationId: myCoolApi
      description: Do cool stuff
      responses:
        200:
          description: Results
          content:
            'application/json':
              schema:
                $ref: '#/components/schemas/MyCoolContent'
components:
  schemas:
    MyCoolContent:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              dt:
                type: string
                format: date
            required:
              - dt
        package:
          type: string
      required:
        - results

...the generated Python runtime includes a MyCoolContentInner model whose dt field is of type date. As I understand, the user's handler function implementation could return for example:

def do_the_thing(input: MyCoolApiRequest, **kwargs) -> MyCoolApiOperationResponses:
    return Response.success(
        body=MyCoolContent(
            results=[
                MyCoolContentInner(dt=date(2025, 6, 6))
            ],
        ),
    )

handler = my_cool_api_handler(interceptors=INTERCEPTORS)(do_the_thing)

...which would satisfy the generated typing of MyCoolApiOperationResponses.

Current Behavior

Reading in date parameters is fine, but if a handler's Response body uses a model containing a date field then PDK's generated MyCoolContentInner.to_json implementation will call Pydantic model_dump (which preserves the date object in the output), followed by built-in json.dumps() - which fails to stringify dates and throws an error like:

[ERROR] TypeError: Object of type date is not JSON serializable

Reproduction Steps

I don't have a full shareable code example, but creating and deploying any TypeSafeApi that returns a field of OpenAPI type: string, format: date (spec similar to the above) should demonstrate it?

I think creating the handler directly in the API project (e.g. with x-handler: language: python) should be simplest and will still illustrate the problem.

Possible Solution

Switching to Pydantic's model_dump_json() seems to resolve this as they document an explicit example of datetime handling.

Additional Information/Context

No response

PDK version used

0.23.45 (but as discussed, the code seems same in current mainline)

What languages are you seeing this issue on?

Python

Environment details (OS name and version, etc.)

macOS 15.5

athewsey avatar Jul 01 '25 04:07 athewsey

My interim workaround for this is to derive patch classes from the auto-generated models - using the TODO pattern to fix the serialization behaviour - and return those instead from my Lambda.

I ended up patching both the outer model and the inner one, but I think only the outer/top-level one is actually necessary - because that's what to_json() gets called on.

from my_cool_api_python_runtime.models import *

class MyCoolContentResultsInnerPatch(MyCoolContentResultsInner):
    def to_json(self) -> str:
        return self.model_dump_json(by_alias=True, exclude_unset=True)

class MyCoolContentPatch(MyCoolContent):
    def to_json(self) -> str:
        return self.model_dump_json(by_alias=True, exclude_unset=True)

def do_the_thing(input: MyCoolApiRequest, **kwargs) -> MyCoolApiOperationResponses:
    return Response.success(
        body=MyCoolContentPatch(
            results=[
                MyCoolContentResultsInnerPatch(dt=date(2025, 6, 6))
            ],
        ),
    )

handler = my_cool_api_handler(interceptors=INTERCEPTORS)(do_the_thing)

athewsey avatar Jul 01 '25 16:07 athewsey

Thanks for raising this! Glad to hear you have a workaround.

I believe that TODO came from a like-for-like copy of the Java-based OpenAPI Generator which Type Safe API used to use, and we had to keep parity when we moved to our own node-based code generation.

I think in theory swapping to model_dump_json will only improve things, but it needs a bit of testing. We're low on bandwidth at the moment but would be open to a PR!

Cheers, Jack

cogwirrel avatar Jul 02 '25 03:07 cogwirrel