diff --git a/README.md b/README.md index d284fad59..e00de1e84 100644 --- a/README.md +++ b/README.md @@ -432,7 +432,7 @@ class IPv4AddressJSONEncoder(AdvancedJSONEncoder): class IPv4AddressJSONTypeConverter(JSONTypeConverter): def to_typed_value( self, hint: Type, value: Any - ) -> Union[Optional[Any], _JSONTypeConverterUnhandled]: + ) -> Union[Optional[Any], JSONTypeConverterUnhandled]: if issubclass(hint, ipaddress.IPv4Address): return ipaddress.IPv4Address(value) return JSONTypeConverter.Unhandled diff --git a/temporalio/converter/__init__.py b/temporalio/converter/__init__.py index 3ca6a3507..3821cbd68 100644 --- a/temporalio/converter/__init__.py +++ b/temporalio/converter/__init__.py @@ -31,6 +31,7 @@ JSONPlainPayloadConverter, JSONProtoPayloadConverter, JSONTypeConverter, + JSONTypeConverterUnhandled, PayloadConverter, value_to_type, ) @@ -76,6 +77,7 @@ "JSONPlainPayloadConverter", "JSONProtoPayloadConverter", "JSONTypeConverter", + "JSONTypeConverterUnhandled", "PayloadCodec", "PayloadConverter", "PayloadLimitsConfig", diff --git a/temporalio/converter/_payload_converter.py b/temporalio/converter/_payload_converter.py index d2effc9d1..8ee85ef72 100644 --- a/temporalio/converter/_payload_converter.py +++ b/temporalio/converter/_payload_converter.py @@ -548,7 +548,10 @@ def default(self, o: Any) -> Any: return super().default(o) -_JSONTypeConverterUnhandled = NewType("_JSONTypeConverterUnhandled", object) +JSONTypeConverterUnhandled = NewType("JSONTypeConverterUnhandled", object) +"""Type of :py:attr:`JSONTypeConverter.Unhandled`.""" + +_JSONTypeConverterUnhandled = JSONTypeConverterUnhandled class JSONTypeConverter(ABC): @@ -556,7 +559,9 @@ class JSONTypeConverter(ABC): result (e.g. scalar, list, or dict) to a known type. """ - Unhandled = _JSONTypeConverterUnhandled(object()) + Unhandled: ClassVar[JSONTypeConverterUnhandled] = JSONTypeConverterUnhandled( + object() + ) """Sentinel value that must be used as the result of :py:meth:`to_typed_value` to say the given type is not handled by this converter.""" @@ -564,7 +569,7 @@ class JSONTypeConverter(ABC): @abstractmethod def to_typed_value( self, hint: type, value: Any - ) -> Any | None | _JSONTypeConverterUnhandled: + ) -> Any | None | JSONTypeConverterUnhandled: """Convert the given value to a type based on the given hint. Args: diff --git a/tests/test_converter.py b/tests/test_converter.py index dfe8860d1..10365f9c1 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -16,6 +16,8 @@ Dict, # type:ignore[reportDeprecated] Literal, NewType, + get_args, + get_type_hints, ) from uuid import UUID, uuid4 @@ -40,12 +42,12 @@ DefaultPayloadConverter, JSONPlainPayloadConverter, JSONTypeConverter, + JSONTypeConverterUnhandled, PayloadCodec, decode_search_attributes, encode_search_attribute_values, value_to_type, ) -from temporalio.converter._payload_converter import _JSONTypeConverterUnhandled from temporalio.exceptions import ( ApplicationError, FailureError, @@ -869,12 +871,22 @@ def default(self, o: Any) -> Any: class IPv4AddressJSONTypeConverter(JSONTypeConverter): def to_typed_value( self, hint: type, value: Any - ) -> Any | None | _JSONTypeConverterUnhandled: + ) -> Any | None | JSONTypeConverterUnhandled: if inspect.isclass(hint) and issubclass(hint, ipaddress.IPv4Address): return ipaddress.IPv4Address(value) return JSONTypeConverter.Unhandled +def test_json_type_converter_unhandled_type_public(): + return_type = get_type_hints(JSONTypeConverter.to_typed_value)["return"] + + assert JSONTypeConverterUnhandled.__name__ == "JSONTypeConverterUnhandled" + assert JSONTypeConverterUnhandled in get_args(return_type) + assert JSONTypeConverterUnhandled(JSONTypeConverter.Unhandled) is ( + JSONTypeConverter.Unhandled + ) + + async def test_json_type_converter(): addr = ipaddress.IPv4Address("1.2.3.4") custom_conv = dataclasses.replace(