From 68393f5e3bf9c11dedec6c6b7858ade42d56f02a Mon Sep 17 00:00:00 2001 From: Web3RJ Date: Sat, 31 Oct 2026 13:10:11 +0530 Subject: [PATCH 1/4] Exclude computed fields from Pydantic payload converter serialization Updated to_payload method to use SchemaSerializer for JSON serialization. --- temporalio/contrib/pydantic.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/temporalio/contrib/pydantic.py b/temporalio/contrib/pydantic.py index bb8fe0010..78ef6fc80 100644 --- a/temporalio/contrib/pydantic.py +++ b/temporalio/contrib/pydantic.py @@ -18,7 +18,7 @@ from typing import Any from pydantic import TypeAdapter -from pydantic_core import SchemaSerializer, to_json +from pydantic_core import SchemaSerializer from pydantic_core.core_schema import any_schema import temporalio.api.common.v1 @@ -104,17 +104,24 @@ def encoding(self) -> str: def to_payload(self, value: Any) -> temporalio.api.common.v1.Payload | None: """See base class. - Uses ``pydantic_core.to_json`` to serialize ``value`` to JSON. + Serializes ``value`` to JSON with a ``pydantic_core`` schema + serializer configured to match ``pydantic_core.to_json`` defaults. - See - https://docs.pydantic.dev/latest/api/pydantic_core/#pydantic_core.to_json. + Computed fields are excluded so payloads always round-trip, even for + models with ``extra="forbid"`` which would reject them on read. """ data = ( self._schema_serializer.to_json( - value, by_alias=True, exclude_unset=self._to_json_options.exclude_unset + value, + by_alias=True, + exclude_unset=self._to_json_options.exclude_unset, + exclude_computed_fields=True, ) if self._to_json_options - else to_json(value) + # by_alias=True matches the pydantic_core.to_json default + else self._schema_serializer.to_json( + value, by_alias=True, exclude_computed_fields=True + ) ) return temporalio.api.common.v1.Payload( metadata={"encoding": self.encoding.encode()}, data=data From 5439f376552d7d0d6d49aa09c7a0e93221a9ea2c Mon Sep 17 00:00:00 2001 From: Web3RJ Date: Sat, 31 Oct 2026 13:11:57 +0530 Subject: [PATCH 2/4] Require pydantic>=2.11 for exclude_computed_fields support Updated pydantic dependency version to 2.11.0. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a87c0790d..034cfb38c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ [project.optional-dependencies] grpc = ["grpcio>=1.48.2,<2"] opentelemetry = ["opentelemetry-api>=1.26,<2", "opentelemetry-sdk>=1.26,<2"] -pydantic = ["pydantic>=2.0.0,<3"] +pydantic = ["pydantic>=2.11.0,<3"] openai-agents = ["temporalio-openai-agents>=1.0.0"] google-adk = ["temporalio-google-adk[mcp]>=0.0.1"] langgraph = ["temporalio-langgraph>=0.1.0"] From b0d8381ef91ab97ca21b576078fdf23e9eb80e03 Mon Sep 17 00:00:00 2001 From: Web3RJ Date: Sat, 31 Oct 2026 13:17:58 +0530 Subject: [PATCH 3/4] Add round-trip regression test for excluded computed fields Added tests for computed fields in Pydantic models, ensuring they are excluded during serialization. --- tests/contrib/pydantic/test_pydantic.py | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/contrib/pydantic/test_pydantic.py b/tests/contrib/pydantic/test_pydantic.py index c3ab95f18..8ba6025f7 100644 --- a/tests/contrib/pydantic/test_pydantic.py +++ b/tests/contrib/pydantic/test_pydantic.py @@ -128,6 +128,38 @@ def test_to_json_options_exclude_unset_keeps_aliases(): assert converter.from_payload(payload, AliasModel) == AliasModel(orderId=7) +class ComputedFieldModel(BaseModel): + model_config = pydantic.ConfigDict(extra="forbid") + x: int + + @pydantic.computed_field + @property + def double(self) -> int: + return self.x * 2 + + +@pytest.mark.parametrize( + "converter", + [ + PydanticJSONPlainPayloadConverter(), + PydanticJSONPlainPayloadConverter( + temporalio.contrib.pydantic.ToJsonOptions(exclude_unset=True) + ), + ], +) +def test_computed_fields_excluded_for_round_trip( + converter: PydanticJSONPlainPayloadConverter, +): + # Computed fields are derived on read, so serializing them breaks models + # with extra="forbid" which reject them as unexpected input. + payload = converter.to_payload(ComputedFieldModel(x=21)) + assert payload is not None + assert payload.data == b'{"x":21}' + assert converter.from_payload(payload, ComputedFieldModel) == ComputedFieldModel( + x=21 + ) + + async def test_pydantic_data_converter_pickle(): restored = pickle.loads(pickle.dumps(pydantic_data_converter)) value = {"a": 1} From 0eec5cb133120e8e18990a3fc9c049c6b65a6e4a Mon Sep 17 00:00:00 2001 From: Web3RJ Date: Sat, 31 Oct 2026 13:19:10 +0530 Subject: [PATCH 4/4] Add changelog entry for computed fields fix Exclude computed fields from Pydantic JSON serialization to ensure models round-trip correctly with extra='forbid'. Requires pydantic>=2.11. --- changelog/fixed/tidy-computed-teapot.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/fixed/tidy-computed-teapot.md diff --git a/changelog/fixed/tidy-computed-teapot.md b/changelog/fixed/tidy-computed-teapot.md new file mode 100644 index 000000000..6509d06a5 --- /dev/null +++ b/changelog/fixed/tidy-computed-teapot.md @@ -0,0 +1 @@ +Exclude computed fields when the Pydantic payload converter serializes to JSON, so models using computed fields round-trip even with extra="forbid". Requires pydantic>=2.11.