Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions temporalio/converter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
JSONPlainPayloadConverter,
JSONProtoPayloadConverter,
JSONTypeConverter,
JSONTypeConverterUnhandled,
PayloadConverter,
value_to_type,
)
Expand Down Expand Up @@ -76,6 +77,7 @@
"JSONPlainPayloadConverter",
"JSONProtoPayloadConverter",
"JSONTypeConverter",
"JSONTypeConverterUnhandled",
"PayloadCodec",
"PayloadConverter",
"PayloadLimitsConfig",
Expand Down
11 changes: 8 additions & 3 deletions temporalio/converter/_payload_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,23 +548,28 @@ 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):
"""Converter for converting an object from Python :py:func:`json.loads`
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."""

@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:
Expand Down
16 changes: 14 additions & 2 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
Dict, # type:ignore[reportDeprecated]
Literal,
NewType,
get_args,
get_type_hints,
)
from uuid import UUID, uuid4

Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand Down