diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e6788a8..3de56f2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,11 @@ to include examples, links to docs, or any other relevant information. ### Fixed +- Encoding a datetime search attribute without a timezone now raises + `ValueError("Timezone must be present on all search attribute dates")` on + the typed path, matching the deprecated untyped encoder, instead of sending + a naive ISO string that the server rejects with `BadSearchAttributes`. + - `temporalio.contrib.opentelemetry`: `TracingInterceptor` and `OpenTelemetryInterceptor` no longer log `Failed to detach context` when a context is torn down on a different thread while OpenTelemetry's threading instrumentation (enabled by strands, among others) is active; a diff --git a/temporalio/converter/_search_attributes.py b/temporalio/converter/_search_attributes.py index 4ec154d6f..4a8191bbd 100644 --- a/temporalio/converter/_search_attributes.py +++ b/temporalio/converter/_search_attributes.py @@ -61,6 +61,8 @@ def encode_typed_search_attribute_value( ) # datetime needs to be in isoformat if isinstance(value, datetime): + if value.tzinfo is None: + raise ValueError("Timezone must be present on all search attribute dates") value = value.isoformat() # We'll do an extra sanity check for keyword list and check every value if isinstance(value, Sequence): diff --git a/tests/test_converter.py b/tests/test_converter.py index 08e511d03..30b1351a9 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -52,6 +52,7 @@ create_payload_validation_error, decode_search_attributes, encode_search_attribute_values, + encode_typed_search_attribute_value, transfer_type_convertible, value_to_type, ) @@ -467,6 +468,18 @@ def test_encode_search_attribute_values(): encode_search_attribute_values(["foo", 123]) # type: ignore[arg-type] +def test_encode_typed_search_attribute_value_datetime_requires_timezone(): + key = temporalio.common.SearchAttributeKey.for_datetime("checkout_time") + with pytest.raises(ValueError, match="Timezone must be present"): + encode_typed_search_attribute_value( + key, datetime(2024, 7, 5, 15, 43, 7, 875302) + ) + payload = encode_typed_search_attribute_value( + key, datetime(2024, 7, 5, 15, 43, 7, 875302, tzinfo=timezone.utc) + ) + assert payload.metadata["type"] == b"Datetime" + + def test_decode_search_attributes(): """Tests decode from protobuf for python types"""