Skip to content

fix(postgrest): serialize generated typed dict payloads (#1443)#1483

Open
SM1LE-X wants to merge 1 commit intosupabase:mainfrom
SM1LE-X:fix/generated-typeddict-json-serialization
Open

fix(postgrest): serialize generated typed dict payloads (#1443)#1483
SM1LE-X wants to merge 1 commit intosupabase:mainfrom
SM1LE-X:fix/generated-typeddict-json-serialization

Conversation

@SM1LE-X
Copy link
Copy Markdown
Contributor

@SM1LE-X SM1LE-X commented May 5, 2026

What kind of change does this PR introduce?

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Issue

Fixes #1443

What is the current behavior?

Generated Python REST types can produce TypedDict write payloads with PostgreSQL-derived Python values such as UUID, datetime, date, and time.

The current insert, upsert, and update signatures only accept the recursive response JSON type, so generated TypedDict payloads are rejected by type checkers. Widening the annotation alone does not fix the runtime path because request execution still relies on stdlib JSON serialization through HTTPX, which raises TypeError for UUID and datetime values.

What is the new behavior?

This PR keeps response JSON parsing strict and adds a separate write payload type for row-shaped request bodies.

Write methods now accept generated-style payloads, and PostgREST serializes supported generated values before sending the HTTP request body:

  • UUID is serialized with str(value).
  • datetime, date, and time are serialized with value.isoformat().
  • Existing HTTPX JSON behavior is preserved: compact separators, UTF-8 output, Content-Type: application/json when absent, and allow_nan=False.

Changes made:

  • Added WriteJSON for Mapping[str, Any] | Sequence[Mapping[str, Any]] write payloads.
  • Updated sync and async insert, upsert, and update to use WriteJSON.
  • Encoded request JSON in PostgREST before calling HTTPX so generated TypedDict values serialize correctly at runtime.
  • Added sync and async tests for generated-style TypedDict payloads containing UUID, datetime, date, and time.
  • Added coverage for preserving an existing Content-Type and rejecting non-finite floats.

Verification

  • uv run --package postgrest mypy src/postgrest tests
  • uv run --package postgrest pytest --cov=./ --cov-report=xml -vv - 292 passed
  • uv run ruff check <touched files>
  • uv run ruff format --check <touched files>
  • Pyright probe for generated-style TypedDict payloads - 0 errors

Checklist:

  • I have read the CONTRIBUTING document.
  • My code follows the code style of this project.

Copilot AI review requested due to automatic review settings May 5, 2026 19:12
@SM1LE-X SM1LE-X requested review from a team and o-santi as code owners May 5, 2026 19:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the PostgREST write path so generated Python TypedDict payloads can be used with insert-like operations when they contain PostgreSQL-derived Python values such as UUID and temporal types. It fits into the request-builder layer by widening write payload typing and replacing httpx(json=...) serialization with an explicit JSON encoding step before requests are sent.

Changes:

  • Added request-body serialization for UUID, datetime, date, and time values before dispatching non-GET/HEAD requests.
  • Introduced new write-payload typing for insert, upsert, and update in both sync and async request builders.
  • Added sync/async request-builder tests for generated TypedDict payload serialization and related request-body behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/postgrest/tests/_sync/test_request_builder.py Adds sync tests for generated TypedDict inserts, request body encoding, content type preservation, and NaN rejection.
src/postgrest/tests/_async/test_request_builder.py Adds async tests covering generated TypedDict insert serialization.
src/postgrest/src/postgrest/types.py Introduces the new internal request/write payload type aliases.
src/postgrest/src/postgrest/base_request_builder.py Implements custom request JSON serialization and routes write requests through encoded request content.
src/postgrest/src/postgrest/_sync/request_builder.py Updates sync insert/upsert/update signatures to use the new write-payload type.
src/postgrest/src/postgrest/_async/request_builder.py Updates async insert/upsert/update signatures to use the new write-payload type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/postgrest/src/postgrest/types.py Outdated
)
JSONAdapter: TypeAdapter = TypeAdapter(JSON)
RequestJSON = Any
WriteJSON = Union[Mapping[str, Any], Sequence[Mapping[str, Any]]]
Comment thread src/postgrest/tests/_sync/test_request_builder.py
Comment on lines +172 to +204
async def test_insert_serializes_generated_typeddict_values(self):
inserted_id = uuid4()
payload: GeneratedMovieInsert = {
"id": inserted_id,
"created_at": datetime(2026, 4, 6, 12, 30, tzinfo=timezone.utc),
"release_date": date(2026, 4, 6),
"starts_at": time(12, 30),
"name": "Inception",
}

async def handler(request: Request) -> Response:
assert request.headers["content-type"] == "application/json"
assert request.headers["content-length"] == str(len(request.content))
assert request.content == (
b'{"id":"'
+ str(inserted_id).encode()
+ b'","created_at":"2026-04-06T12:30:00+00:00",'
+ b'"release_date":"2026-04-06","starts_at":"12:30:00",'
+ b'"name":"Inception"}'
)
return Response(201, json=[{"id": str(inserted_id)}], request=request)

async with AsyncClient(transport=MockTransport(handler)) as client:
builder = AsyncRequestBuilder(
client,
URL("https://example.supabase.co/rest/v1/movies"),
Headers(),
None,
)

response = await builder.insert(payload).execute()

assert response.data == [{"id": str(inserted_id)}]
@SM1LE-X SM1LE-X changed the title fix(postgrest): serialize generated typed dict payloads fix(postgrest): serialize generated typed dict payloads (#1443) May 5, 2026
@SM1LE-X SM1LE-X force-pushed the fix/generated-typeddict-json-serialization branch from 1ea951d to 3fadcf9 Compare May 5, 2026 19:30
@SM1LE-X
Copy link
Copy Markdown
Contributor Author

SM1LE-X commented May 5, 2026

Addressed the write-surface coverage gap and tightened the payload alias:

  • added sync and async regression tests for generated TypedDict serialization through upsert
  • added sync and async regression tests for generated TypedDict serialization through update
  • kept the shared serializer guard covered with an unsupported-object test so arbitrary objects still fail before transport
  • replaced the Any write-payload values with object; TypedDict is exposed to type checkers as Mapping[str, object], so this keeps generated row types assignable without using Any

Verification after the update:

  • focused serialization tests: 7 passed
  • full PostgREST tests: 292 passed
  • mypy: no issues in 30 source files
  • ruff check/format on touched files: clean
  • Pyright generated-TypedDict probe for sync/async insert, upsert, and update: 0 errors

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

insert does not expect TypedDict provided by generated types

2 participants